From 9dda678aebcac2980f7bb23a7a0ae6d3ef43fbeb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Dec 2021 11:24:34 +0100 Subject: [PATCH 001/240] Parse open EXR header --- src/ImageSharp/Configuration.cs | 11 +- .../Formats/Bmp/BmpArrayFileHeader.cs | 5 +- src/ImageSharp/Formats/Bmp/BmpConstants.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 2 - src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpFormat.cs | 6 +- .../Formats/Bmp/BmpImageFormatDetector.cs | 5 +- src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs | 20 +- .../Formats/OpenExr/ExrAttribute.cs | 26 ++ src/ImageSharp/Formats/OpenExr/ExrBox2i.cs | 24 ++ .../Formats/OpenExr/ExrChannelInfo.cs | 30 ++ .../Formats/OpenExr/ExrCompression.cs | 10 + .../Formats/OpenExr/ExrConfigurationModule.cs | 18 ++ .../Formats/OpenExr/ExrConstants.cs | 28 ++ src/ImageSharp/Formats/OpenExr/ExrDecoder.cs | 61 +++++ .../Formats/OpenExr/ExrDecoderCore.cs | 256 ++++++++++++++++++ src/ImageSharp/Formats/OpenExr/ExrFormat.cs | 38 +++ src/ImageSharp/Formats/OpenExr/ExrHeader.cs | 71 +++++ .../Formats/OpenExr/ExrImageFormatDetector.cs | 31 +++ .../Formats/OpenExr/ExrLineOrder.cs | 14 + src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 29 ++ .../Formats/OpenExr/ExrPixelType.cs | 23 ++ .../Formats/OpenExr/ExrThrowHelper.cs | 26 ++ .../Formats/OpenExr/IExrDecoderOptions.cs | 12 + src/ImageSharp/Formats/OpenExr/README.md | 4 + .../Pbm/BufferedReadStreamExtensions.cs | 26 ++ 26 files changed, 746 insertions(+), 36 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrAttribute.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrBox2i.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrCompression.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrConstants.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrDecoder.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrFormat.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrHeader.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrMetadata.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrPixelType.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs create mode 100644 src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/OpenExr/README.md diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index ca83b0764..a3e0c9948 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the that is currently in use. /// - public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); + public ImageFormatManager ImageFormatsManager { get; set; } = new(); /// /// Gets or sets the that is currently in use. @@ -192,7 +193,7 @@ namespace SixLabors.ImageSharp /// Creates a shallow copy of the . /// /// A new configuration instance. - public Configuration Clone() => new Configuration + public Configuration Clone() => new() { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, StreamProcessingBufferSize = this.StreamProcessingBufferSize, @@ -214,9 +215,10 @@ namespace SixLabors.ImageSharp /// . /// . /// . + /// . /// /// The default configuration of . - internal static Configuration CreateDefaultInstance() => new Configuration( + internal static Configuration CreateDefaultInstance() => new( new PngConfigurationModule(), new JpegConfigurationModule(), new GifConfigurationModule(), @@ -224,6 +226,7 @@ namespace SixLabors.ImageSharp new PbmConfigurationModule(), new TgaConfigurationModule(), new TiffConfigurationModule(), - new WebpConfigurationModule()); + new WebpConfigurationModule(), + new ExrConfigurationModule()); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs index 233857247..d68841ad1 100644 --- a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs @@ -45,9 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public short ScreenHeight { get; } - public static BmpArrayFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } + public static BmpArrayFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 0b9499eeb..4684406b2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Defines constants relating to BMPs + /// Defines constants relating to BMP images. /// internal static class BmpConstants { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 129b3a1aa..4c433b203 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -4,8 +4,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 41adc1cff..fe31a686b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets the dimensions of the image. /// - public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index d92a73104..2b24c4387 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets the current instance. /// - public static BmpFormat Instance { get; } = new BmpFormat(); + public static BmpFormat Instance { get; } = new(); /// public string Name => "BMP"; @@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp public IEnumerable FileExtensions => BmpConstants.FileExtensions; /// - public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata(); + public BmpMetadata CreateDefaultFormatMetadata() => new(); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index b380486a3..82376a30c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public int HeaderSize => 2; /// - public IImageFormat DetectFormat(ReadOnlySpan header) - { - return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; - } + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; private bool IsSupportedFileFormat(ReadOnlySpan header) { diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index 0d0c05c9f..6cd9d19f5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -279,15 +279,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The data to parse. /// The parsed header. /// - public static BmpInfoHeader ParseCore(ReadOnlySpan data) - { - return new BmpInfoHeader( + public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)), height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)), planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)), bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2))); - } /// /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height @@ -296,15 +293,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The data to parse. /// The parsed header. /// - public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) - { - return new BmpInfoHeader( + public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); - } /// /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). @@ -312,9 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The data to parse. /// The parsed header. /// - public static BmpInfoHeader ParseV3(ReadOnlySpan data) - { - return new BmpInfoHeader( + public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), @@ -326,7 +318,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); - } /// /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. @@ -336,9 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Indicates, if the alpha bitmask is present. /// The parsed header. /// - public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) - { - return new BmpInfoHeader( + public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), @@ -354,7 +343,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); - } /// /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs new file mode 100644 index 000000000..66423f50d --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] + internal class ExrAttribute + { + public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); + + public ExrAttribute(string name, string type, int length) + { + this.Name = name; + this.Type = type; + this.Length = length; + } + + public string Name { get; } + + public string Type { get; } + + public int Length { get; } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs new file mode 100644 index 000000000..c45dc087e --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal struct ExrBox2i + { + public ExrBox2i(int xMin, int yMin, int xMax, int yMax) + { + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + } + + public int xMin { get; } + + public int yMin { get; } + + public int xMax { get; } + + public int yMax { get; } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs new file mode 100644 index 000000000..81b40ea66 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal readonly struct ExrChannelInfo + { + public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) + { + this.ChannelName = channelName; + this.PixelType = pixelType; + this.PLinear = pLinear; + this.XSampling = xSampling; + this.YSampling = ySampling; + } + + public string ChannelName { get; } + + public ExrPixelType PixelType { get; } + + public byte PLinear { get; } + + public int XSampling { get; } + + public int YSampling { get; } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs new file mode 100644 index 000000000..3fa8cadfd --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrCompression : int + { + None = 0 + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs new file mode 100644 index 000000000..ff0d3ee6a --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. + /// + public sealed class ExrConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs new file mode 100644 index 000000000..b3f1a3424 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Defines constants relating to OpenExr images. + /// + internal static class ExrConstants + { + /// + /// The list of mimetypes that equate to a OpenExr image. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" }; + + /// + /// The list of file extensions that equate to a OpenExr image. + /// + public static readonly IEnumerable FileExtensions = new[] { "exr" }; + + /// + /// The magick bytes identifying an OpenExr image. + /// + public static readonly int MagickBytes = 20000630; + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs new file mode 100644 index 000000000..0c645d71e --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image decoder for generating an image out of a OpenExr stream. + /// + public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector + { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new ExrDecoderCore(configuration, this); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new ExrDecoderCore(configuration, this); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new ExrDecoderCore(configuration, this).Identify(configuration, stream); + } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + return new ExrDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs new file mode 100644 index 000000000..c5c942506 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Performs the OpenExr decoding operation. + /// + internal sealed class ExrDecoderCore : IImageDecoderInternals + { + /// + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The bitmap decoder options. + /// + private readonly IExrDecoderOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public ExrDecoderCore(Configuration configuration, IExrDecoderOptions options) + { + this.Configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } + + /// + public Configuration Configuration { get; } + + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new(this.Width, this.Height); + + private int Width { get; set; } + + private int Height { get; set; } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + // Skip over the magick bytes. + stream.Read(this.buffer, 0, 4); + + // Read version number. + byte version = (byte)stream.ReadByte(); + if (version != 2) + { + ExrThrowHelper.ThrowNotSupportedVersion(); + } + + // Next three bytes contain info's about the image. + // We ignore those for now. + stream.Read(this.buffer, 0, 3); + + ExrHeader header = this.ParseHeader(stream); + + if (!header.IsValid()) + { + ExrThrowHelper.ThrowInvalidImageHeader(); + } + + if (header.Channels.Count != 3) + { + ExrThrowHelper.ThrowNotSupported("Only 3 channels are supported!"); + } + + this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1; + this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1; + + // TODO: calculate bits per pixel. + int bitsPerPixel = 48; + + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + } + + private ExrHeader ParseHeader(BufferedReadStream stream) + { + ExrAttribute attribute = this.ReadAttribute(stream); + var header = new ExrHeader(); + + while (!attribute.Equals(ExrAttribute.EmptyAttribute)) + { + switch (attribute.Name) + { + case "channels": + IList channels = this.ParseChannelList(stream, attribute.Length); + header.Channels = channels; + break; + case "compression": + var compression = (ExrCompression)stream.ReadByte(); + header.Compression = compression; + break; + case "dataWindow": + ExrBox2i dataWindow = this.ReadBox2i(stream); + header.DataWindow = dataWindow; + break; + case "displayWindow": + ExrBox2i displayWindow = this.ReadBox2i(stream); + header.DisplayWindow = displayWindow; + break; + case "lineOrder": + var lineOrder = (ExrLineOrder)stream.ReadByte(); + header.LineOrder = lineOrder; + break; + case "pixelAspectRatio": + float aspectRatio = stream.ReadSingle(this.buffer); + header.AspectRatio = aspectRatio; + break; + case "screenWindowCenter": + float screenWindowCenterX = stream.ReadSingle(this.buffer); + float screenWindowCenterY = stream.ReadSingle(this.buffer); + header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); + break; + case "screenWindowWidth": + float screenWindowWidth = stream.ReadSingle(this.buffer); + header.ScreenWindowWidth = screenWindowWidth; + break; + default: + // Skip unknown attribute bytes. + stream.Skip(attribute.Length); + break; + } + + attribute = this.ReadAttribute(stream); + } + + return header; + } + + private ExrAttribute ReadAttribute(BufferedReadStream stream) + { + string attributeName = ReadString(stream); + if (attributeName.Equals(string.Empty)) + { + return ExrAttribute.EmptyAttribute; + } + + string attributeType = ReadString(stream); + + stream.Read(this.buffer, 0, 4); + int attributeSize = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + return new ExrAttribute(attributeName, attributeType, attributeSize); + } + + private ExrBox2i ReadBox2i(BufferedReadStream stream) + { + stream.Read(this.buffer, 0, 4); + int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + stream.Read(this.buffer, 0, 4); + int yMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + stream.Read(this.buffer, 0, 4); + int xMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + stream.Read(this.buffer, 0, 4); + int yMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + return new ExrBox2i(xMin, yMin, xMax, yMax); + } + + private List ParseChannelList(BufferedReadStream stream, int attributeSize) + { + var channels = new List(); + while (attributeSize > 1) + { + ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); + channels.Add(channelInfo); + attributeSize -= bytesRead; + } + + // Last byte should be a null byte. + int byteRead = stream.ReadByte(); + + return channels; + } + + private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) + { + string channelName = ReadString(stream); + bytesRead = channelName.Length + 1; + + stream.Read(this.buffer, 0, 4); + bytesRead += 4; + var pixelType = (ExrPixelType)BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + byte pLinear = (byte)stream.ReadByte(); + byte reserved0 = (byte)stream.ReadByte(); + byte reserved1 = (byte)stream.ReadByte(); + byte reserved2 = (byte)stream.ReadByte(); + bytesRead += 4; + + stream.Read(this.buffer, 0, 4); + bytesRead += 4; + int xSampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + stream.Read(this.buffer, 0, 4); + bytesRead += 4; + int ySampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + + return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); + } + + private static string ReadString(BufferedReadStream stream) + { + var str = new StringBuilder(); + int character = stream.ReadByte(); + if (character == 0) + { + // End of file header reached. + return string.Empty; + } + + while (character != 0) + { + if (character == -1) + { + ExrThrowHelper.ThrowInvalidImageHeader(); + } + + str.Append((char)character); + character = stream.ReadByte(); + } + + return str.ToString(); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs new file mode 100644 index 000000000..2cbce0970 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.Bmp; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. + /// + public sealed class ExrFormat : IImageFormat + { + private ExrFormat() + { + } + + /// + /// Gets the current instance. + /// + public static ExrFormat Instance { get; } = new(); + + /// + public string Name => "EXR"; + + /// + public string DefaultMimeType => "image/x-exr"; + + /// + public IEnumerable MimeTypes => ExrConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => ExrConstants.FileExtensions; + + /// + public ExrMetadata CreateDefaultFormatMetadata() => new(); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs new file mode 100644 index 000000000..1ed464ebd --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal class ExrHeader + { + public IList Channels { get; set; } + + public ExrCompression? Compression { get; set; } + + public ExrBox2i? DataWindow { get; set; } + + public ExrBox2i? DisplayWindow { get; set; } + + public ExrLineOrder? LineOrder { get; set; } + + public float? AspectRatio { get; set; } + + public float? ScreenWindowWidth { get; set; } + + public PointF? ScreenWindowCenter { get; set; } + + public bool IsValid() + { + if (!this.Compression.HasValue) + { + return false; + } + + if (!this.DataWindow.HasValue) + { + return false; + } + + if (!this.DisplayWindow.HasValue) + { + return false; + } + + if (!this.LineOrder.HasValue) + { + return false; + } + + if (!this.AspectRatio.HasValue) + { + return false; + } + + if (!this.ScreenWindowWidth.HasValue) + { + return false; + } + + if (!this.ScreenWindowCenter.HasValue) + { + return false; + } + + if (this.Channels is null) + { + return false; + } + + return true; + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs new file mode 100644 index 000000000..27230306d --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Detects OpenExr file headers. + /// + public sealed class ExrImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 4; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) + { + int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); + return fileTypeMarker == ExrConstants.MagickBytes; + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs new file mode 100644 index 000000000..1005414f2 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrLineOrder : byte + { + IncreasingY = 0, + + DecreasingY = 1, + + RandomY = 2 + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs new file mode 100644 index 000000000..f3723c542 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Provides OpenExr specific metadata information for the image. + /// + public class ExrMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public ExrMetadata() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private ExrMetadata(ExrMetadata other) + { + } + + /// + public IDeepCloneable DeepClone() => new ExrMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs new file mode 100644 index 000000000..4e3427cf4 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrPixelType : int + { + /// + /// unsigned int (32 bit). + /// + Uint = 0, + + /// + /// half (16 bit floating point). + /// + Half = 1, + + /// + /// float (32 bit floating point). + /// + Float = 2 + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs new file mode 100644 index 000000000..227a96118 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Cold path optimizations for throwing exr format based exceptions. + /// + internal static class ExrThrowHelper + { + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs new file mode 100644 index 000000000..e61ce8537 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image decoder options for decoding OpenExr streams. + /// + internal interface IExrDecoderOptions + { + } +} diff --git a/src/ImageSharp/Formats/OpenExr/README.md b/src/ImageSharp/Formats/OpenExr/README.md new file mode 100644 index 000000000..c71ab113d --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/README.md @@ -0,0 +1,4 @@ +### Some useful links for documentation about the OpenEXR format: + +- [Technical Introduction](https://openexr.readthedocs.io/en/latest/TechnicalIntroduction.html) +- [OpenExr file layout](https://openexr.readthedocs.io/en/latest/OpenEXRFileLayout.html) \ No newline at end of file diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index 581d3e592..5aa6430d1 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -1,7 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers.Binary; using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Formats.Pbm @@ -61,5 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm return value; } + + /// + /// Reads a float. + /// + /// The stream to read from. + /// A scratch buffer of size 4 bytes. + /// The byte order. Defaults to little endian. + /// the value. + public static float ReadSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian) + { + stream.Read(scratch, 0, 4); + int intValue; + if (byteOrder == ByteOrder.LittleEndian) + { + intValue = BinaryPrimitives.ReadInt32LittleEndian(scratch); + } + else + { + intValue = BinaryPrimitives.ReadInt32BigEndian(scratch); + } + + return Unsafe.As(ref intValue); + } } } From a27696abdbcf07b2ae4752840200f863ddebf42f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 28 Dec 2021 19:14:01 +0100 Subject: [PATCH 002/240] Decode uncompressed EXR images with 16 bit pixel data for each channel --- .../Formats/OpenExr/ExrDecoderCore.cs | 134 +++++++++++++++++- .../Pbm/BufferedReadStreamExtensions.cs | 28 +++- 2 files changed, 153 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index c5c942506..ea40857d8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Text; @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// /// Reusable buffer. /// - private readonly byte[] buffer = new byte[4]; + private readonly byte[] buffer = new byte[8]; /// /// Used for allocating memory during processing operations. @@ -34,6 +35,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// private readonly IExrDecoderOptions options; + /// + /// The metadata. + /// + private ImageMetadata metadata; + /// /// Initializes a new instance of the class. /// @@ -58,12 +64,102 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private int Height { get; set; } + private IList Channels { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + where TPixel : unmanaged, IPixel + { + ExrHeader header = this.ReadExrHeader(stream); + + int bitsPerPixel = CalculateBitsPerPixel(header.Channels); + + var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + // TODO: we for now assume the image pixel type is HalfSingle. + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); + Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + + TPixel color = default; + for (int y = 0; y < this.Height; y++) + { + stream.Read(this.buffer, 0, 8); + ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + long nextRowOffsetPosition = stream.Position; + + stream.Position = (long)rowOffset; + stream.Read(this.buffer, 0, 4); + uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + + stream.Read(this.buffer, 0, 4); + uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + { + switch (this.Channels[channelIdx].ChannelName) + { + case "R": + for (int x = 0; x < this.Width; x++) + { + redPixelData[x] = stream.ReadHalfSingle(this.buffer); + } + + break; + + case "B": + for (int x = 0; x < this.Width; x++) + { + bluePixelData[x] = stream.ReadHalfSingle(this.buffer); + } + + break; + + case "G": + for (int x = 0; x < this.Width; x++) + { + greenPixelData[x] = stream.ReadHalfSingle(this.buffer); + } + + break; + + default: + // Skip unknown channel. + // TODO: can we assume the same data size as the others here? + stream.Position += this.Width * 2; + break; + } + } + + stream.Position = nextRowOffsetPosition; + + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } + } + + return image; + } /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + ExrHeader header = this.ReadExrHeader(stream); + + int bitsPerPixel = CalculateBitsPerPixel(header.Channels); + + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + } + + private ExrHeader ReadExrHeader(BufferedReadStream stream) { // Skip over the magick bytes. stream.Read(this.buffer, 0, 4); @@ -93,11 +189,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1; this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1; + this.Channels = header.Channels; - // TODO: calculate bits per pixel. - int bitsPerPixel = 48; + this.metadata = new ImageMetadata(); - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + return header; } private ExrHeader ParseHeader(BufferedReadStream stream) @@ -110,11 +206,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (attribute.Name) { case "channels": - IList channels = this.ParseChannelList(stream, attribute.Length); + IList channels = this.ReadChannelList(stream, attribute.Length); header.Channels = channels; break; case "compression": var compression = (ExrCompression)stream.ReadByte(); + if (compression != ExrCompression.None) + { + ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported"); + } + header.Compression = compression; break; case "dataWindow": @@ -187,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return new ExrBox2i(xMin, yMin, xMax, yMax); } - private List ParseChannelList(BufferedReadStream stream, int attributeSize) + private List ReadChannelList(BufferedReadStream stream, int attributeSize) { var channels = new List(); while (attributeSize > 1) @@ -229,6 +330,25 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); } + private static int CalculateBitsPerPixel(IList channels) + { + int bitsPerPixel = 0; + for (int i = 0; i < channels.Count; i++) + { + ExrChannelInfo channel = channels[0]; + if (channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint) + { + bitsPerPixel += 32; + } + else if (channel.PixelType == ExrPixelType.Half) + { + bitsPerPixel += 16; + } + } + + return bitsPerPixel; + } + private static string ReadString(BufferedReadStream stream) { var str = new StringBuilder(); diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index 5aa6430d1..95095edc6 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm { @@ -66,12 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - /// Reads a float. + /// Reads a 32 bit float. /// /// The stream to read from. /// A scratch buffer of size 4 bytes. /// The byte order. Defaults to little endian. - /// the value. + /// the float value. public static float ReadSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian) { stream.Read(scratch, 0, 4); @@ -87,5 +88,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm return Unsafe.As(ref intValue); } + + /// + /// Reads a 16 bit float. + /// + /// The stream to read from. + /// A scratch buffer of size 2 bytes. + /// The byte order. Defaults to little endian. + /// The float value. + public static float ReadHalfSingle(this BufferedReadStream stream, Span scratch, ByteOrder byteOrder = ByteOrder.LittleEndian) + { + stream.Read(scratch, 0, 2); + ushort shortValue; + if (byteOrder == ByteOrder.LittleEndian) + { + shortValue = BinaryPrimitives.ReadUInt16LittleEndian(scratch); + } + else + { + shortValue = BinaryPrimitives.ReadUInt16BigEndian(scratch); + } + + return HalfTypeHelper.Unpack(shortValue); + } } } From d3993f7bc0e4fd05a38e8174e140579c2ca53c17 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 28 Dec 2021 22:01:08 +0100 Subject: [PATCH 003/240] Decode EXR images with 32 bit float pixel data --- .../Formats/OpenExr/ExrCompression.cs | 2 +- .../Formats/OpenExr/ExrDecoderCore.cs | 72 +++++++++++++------ 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs index 3fa8cadfd..bcc177bde 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { - internal enum ExrCompression : int + internal enum ExrCompression { None = 0 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index ea40857d8..bbd7636a9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -66,19 +66,25 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private IList Channels { get; set; } + private ExrCompression Compression { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { ExrHeader header = this.ReadExrHeader(stream); + if (this.Compression != ExrCompression.None) + { + ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported"); + } + int bitsPerPixel = CalculateBitsPerPixel(header.Channels); var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); - // TODO: we for now assume the image pixel type is HalfSingle. using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); @@ -102,36 +108,52 @@ namespace SixLabors.ImageSharp.Formats.OpenExr for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - switch (this.Channels[channelIdx].ChannelName) + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) { case "R": - for (int x = 0; x < this.Width; x++) + switch (channel.PixelType) { - redPixelData[x] = stream.ReadHalfSingle(this.buffer); + case ExrPixelType.Half: + this.ReadPixelRowChannelHalfSingle(stream, redPixelData); + break; + case ExrPixelType.Float: + this.ReadPixelRowChannelSingle(stream, redPixelData); + break; } break; case "B": - for (int x = 0; x < this.Width; x++) + switch (channel.PixelType) { - bluePixelData[x] = stream.ReadHalfSingle(this.buffer); + case ExrPixelType.Half: + this.ReadPixelRowChannelHalfSingle(stream, bluePixelData); + break; + case ExrPixelType.Float: + this.ReadPixelRowChannelSingle(stream, bluePixelData); + break; } break; case "G": - for (int x = 0; x < this.Width; x++) + switch (channel.PixelType) { - greenPixelData[x] = stream.ReadHalfSingle(this.buffer); + case ExrPixelType.Half: + this.ReadPixelRowChannelHalfSingle(stream, greenPixelData); + break; + case ExrPixelType.Float: + this.ReadPixelRowChannelSingle(stream, greenPixelData); + break; } break; default: // Skip unknown channel. - // TODO: can we assume the same data size as the others here? - stream.Position += this.Width * 2; + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; break; } } @@ -149,6 +171,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return image; } + private void ReadPixelRowChannelHalfSingle(BufferedReadStream stream, Span channelData) + { + for (int x = 0; x < this.Width; x++) + { + channelData[x] = stream.ReadHalfSingle(this.buffer); + } + } + + private void ReadPixelRowChannelSingle(BufferedReadStream stream, Span channelData) + { + for (int x = 0; x < this.Width; x++) + { + channelData[x] = stream.ReadSingle(this.buffer); + } + } + /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { @@ -182,14 +220,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr ExrThrowHelper.ThrowInvalidImageHeader(); } - if (header.Channels.Count != 3) - { - ExrThrowHelper.ThrowNotSupported("Only 3 channels are supported!"); - } - this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1; this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1; this.Channels = header.Channels; + this.Compression = header.Compression.GetValueOrDefault(); this.metadata = new ImageMetadata(); @@ -210,13 +244,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr header.Channels = channels; break; case "compression": - var compression = (ExrCompression)stream.ReadByte(); - if (compression != ExrCompression.None) - { - ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported"); - } - - header.Compression = compression; + header.Compression = (ExrCompression)stream.ReadByte(); break; case "dataWindow": ExrBox2i dataWindow = this.ReadBox2i(stream); From aaf21434a82dc878981571d2461c7e430e072762 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 29 Dec 2021 11:02:43 +0100 Subject: [PATCH 004/240] Decode EXR images with unsigned int pixel data --- .../Formats/ImageDecoderUtilities.cs | 2 +- src/ImageSharp/Formats/OpenExr/ExrBox2i.cs | 16 +- .../Formats/OpenExr/ExrDecoderCore.cs | 174 +++++++++++++++--- .../Formats/OpenExr/ExrPixelType.cs | 4 +- .../Formats/OpenExr/ExrThrowHelper.cs | 3 + .../PixelImplementations/Rgb96.cs | 172 +++++++++++++++++ 6 files changed, 333 insertions(+), 38 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 5d77fb0c8..49e783ee4 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp.Formats private static InvalidImageContentException DefaultLargeImageExceptionFactory( InvalidMemoryOperationException memoryOperationException, Size dimensions) => - new InvalidImageContentException(dimensions, memoryOperationException); + new(dimensions, memoryOperationException); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs index c45dc087e..6fd610026 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -7,18 +7,18 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { public ExrBox2i(int xMin, int yMin, int xMax, int yMax) { - this.xMin = xMin; - this.yMin = yMin; - this.xMax = xMax; - this.yMax = yMax; + this.XMin = xMin; + this.YMin = yMin; + this.XMax = xMax; + this.YMax = yMax; } - public int xMin { get; } + public int XMin { get; } - public int yMin { get; } + public int YMin { get; } - public int xMax { get; } + public int XMax { get; } - public int yMax { get; } + public int YMax { get; } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index bbd7636a9..79b117b73 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -72,19 +72,35 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - ExrHeader header = this.ReadExrHeader(stream); + this.ReadExrHeader(stream); if (this.Compression != ExrCompression.None) { ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported"); } - int bitsPerPixel = CalculateBitsPerPixel(header.Channels); + ExrPixelType pixelType = this.ValidateChannels(); var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + switch (pixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.DecodeFloatingPointPixelData(stream, pixels); + break; + default: + ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); + break; + } + + return image; + } + + private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); @@ -152,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr default: // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint ? 4 : 2; + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; stream.Position += this.Width * channelDataSizeInBytes; break; } @@ -167,8 +183,84 @@ namespace SixLabors.ImageSharp.Formats.OpenExr pixelRow[x] = color; } } + } - return image; + private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); + Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + + TPixel color = default; + for (int y = 0; y < this.Height; y++) + { + stream.Read(this.buffer, 0, 8); + ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + long nextRowOffsetPosition = stream.Position; + + stream.Position = (long)rowOffset; + stream.Read(this.buffer, 0, 4); + uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + + stream.Read(this.buffer, 0, 4); + uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + { + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) + { + case "R": + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + this.ReadPixelRowChannelUnsignedInt(stream, redPixelData); + break; + } + + break; + + case "B": + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + this.ReadPixelRowChannelUnsignedInt(stream, bluePixelData); + break; + } + + break; + + case "G": + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + this.ReadPixelRowChannelUnsignedInt(stream, greenPixelData); + break; + } + + break; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + break; + } + } + + stream.Position = nextRowOffsetPosition; + + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } + } } private void ReadPixelRowChannelHalfSingle(BufferedReadStream stream, Span channelData) @@ -187,16 +279,63 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void ReadPixelRowChannelUnsignedInt(BufferedReadStream stream, Span channelData) + { + for (int x = 0; x < this.Width; x++) + { + stream.Read(this.buffer, 0, 4); + channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + } + } + /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { ExrHeader header = this.ReadExrHeader(stream); - int bitsPerPixel = CalculateBitsPerPixel(header.Channels); + int bitsPerPixel = this.CalculateBitsPerPixel(); return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); } + private int CalculateBitsPerPixel() + { + int bitsPerPixel = 0; + for (int i = 0; i < this.Channels.Count; i++) + { + ExrChannelInfo channel = this.Channels[0]; + if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) + { + bitsPerPixel += 32; + } + else if (channel.PixelType == ExrPixelType.Half) + { + bitsPerPixel += 16; + } + } + + return bitsPerPixel; + } + + private ExrPixelType ValidateChannels() + { + if (this.Channels.Count == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data expected!"); + } + + ExrPixelType pixelType = this.Channels[0].PixelType; + for (int i = 1; i < this.Channels.Count; i++) + { + if (pixelType != this.Channels[i].PixelType) + { + ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!"); + } + } + + return pixelType; + } + private ExrHeader ReadExrHeader(BufferedReadStream stream) { // Skip over the magick bytes. @@ -220,8 +359,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr ExrThrowHelper.ThrowInvalidImageHeader(); } - this.Width = header.DataWindow.Value.xMax - header.DataWindow.Value.xMin + 1; - this.Height = header.DataWindow.Value.yMax - header.DataWindow.Value.yMin + 1; + this.Width = header.DataWindow.Value.XMax - header.DataWindow.Value.XMin + 1; + this.Height = header.DataWindow.Value.YMax - header.DataWindow.Value.YMin + 1; this.Channels = header.Channels; this.Compression = header.Compression.GetValueOrDefault(); @@ -358,25 +497,6 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); } - private static int CalculateBitsPerPixel(IList channels) - { - int bitsPerPixel = 0; - for (int i = 0; i < channels.Count; i++) - { - ExrChannelInfo channel = channels[0]; - if (channel.PixelType is ExrPixelType.Float or ExrPixelType.Uint) - { - bitsPerPixel += 32; - } - else if (channel.PixelType == ExrPixelType.Half) - { - bitsPerPixel += 16; - } - } - - return bitsPerPixel; - } - private static string ReadString(BufferedReadStream stream) { var str = new StringBuilder(); diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs index 4e3427cf4..fc3dc8ef9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -3,12 +3,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { - internal enum ExrPixelType : int + internal enum ExrPixelType { /// /// unsigned int (32 bit). /// - Uint = 0, + UnsignedInt = 0, /// /// half (16 bit floating point). diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs index 227a96118..ae7654111 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// internal static class ExrThrowHelper { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + [MethodImpl(InliningOptions.ColdPath)] public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs new file mode 100644 index 000000000..405ed40a1 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -0,0 +1,172 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. + /// The color components are stored in red, green, blue + /// + /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Rgb96 : IPixel + { + private const float Max = uint.MaxValue; + + /// + /// Gets or sets the red component. + /// + public uint R; + + /// + /// Gets or sets the green component. + /// + public uint G; + + /// + /// Gets or sets the blue component. + /// + public uint B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb96(uint r, uint g, uint b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + this.R = (uint)(vector.X * Max); + this.G = (uint)(vector.Y * Max); + this.B = (uint)(vector.Z * Max); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new( + this.R / Max, + this.G / Max, + this.B / Max, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + } +} From 799dafe4140eee14c19ecad15fa1c9a8bc6a7eb8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 31 Dec 2021 23:18:52 +0100 Subject: [PATCH 005/240] Encode EXR image with float pixel data --- src/ImageSharp/Formats/Bmp/BmpFileHeader.cs | 5 +- .../Formats/ImageExtensions.Save.cs | 104 ++++++ .../Formats/OpenExr/ExrConfigurationModule.cs | 1 + .../Formats/OpenExr/ExrDecoderCore.cs | 5 +- src/ImageSharp/Formats/OpenExr/ExrEncoder.cs | 38 +++ .../Formats/OpenExr/ExrEncoderCore.cs | 310 ++++++++++++++++++ src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 9 +- .../Formats/OpenExr/ExrPixelType.cs | 5 +- .../Formats/OpenExr/IExrEncoderOptions.cs | 16 + .../Formats/OpenExr/MetadataExtensions.cs | 21 ++ .../PixelImplementations/HalfVector4.cs | 59 +++- 11 files changed, 556 insertions(+), 17 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrEncoder.cs create mode 100644 src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs create mode 100644 src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index acbcdaef3..ab56bd246 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -57,10 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public int Offset { get; } - public static BmpFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } + public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; public void WriteTo(Span buffer) { diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index a6a65aef6..315b942c6 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -847,5 +848,108 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), cancellationToken); + // foo + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path) => SaveAsExr(source, path, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path) => SaveAsExrAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsExrAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path, ExrEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsExr(this Image source, Stream stream) + => SaveAsExr(source, stream, null); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsExrAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsExr(this Image source, Stream stream, ExrEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); + + /// + /// Saves the image to the given stream with the Open Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), + cancellationToken); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs index ff0d3ee6a..d5a03cd5d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// public void Configure(Configuration configuration) { + configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 79b117b73..b0424857c 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -90,6 +90,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr case ExrPixelType.Float: this.DecodeFloatingPointPixelData(stream, pixels); break; + case ExrPixelType.UnsignedInt: + this.DecodeUnsignedIntPixelData(stream, pixels); + break; default: ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); break; @@ -349,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } // Next three bytes contain info's about the image. - // We ignore those for now. + // TODO: We ignore those for now. stream.Read(this.buffer, 0, 3); ExrHeader header = this.ParseHeader(stream); diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs new file mode 100644 index 000000000..71d69596a --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image encoder for writing an image to a stream in the OpenExr Format. + /// + public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions + { + /// + /// Gets or sets the pixel type of the image. + /// + public ExrPixelType? PixelType { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs new file mode 100644 index 000000000..35b381e9b --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -0,0 +1,310 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Image encoder for writing an image to a stream in the OpenExr format. + /// + internal sealed class ExrEncoderCore : IImageEncoderInternals + { + /// + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[8]; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The pixel type of the image. + /// + private ExrPixelType? pixelType; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.pixelType = options.PixelType; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + ExrMetadata exrMetadata = metadata.GetExrMetadata(); + this.pixelType ??= exrMetadata.PixelType; + + int width = image.Width; + int height = image.Height; + ExrPixelType pixelType = ExrPixelType.Float; + var header = new ExrHeader() + { + Compression = ExrCompression.None, + AspectRatio = 1.0f, + DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), + DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), + LineOrder = ExrLineOrder.IncreasingY, + ScreenWindowCenter = new PointF(0.0f, 0.0f), + ScreenWindowWidth = 1, + Channels = new List() + { + new("B", pixelType, 0, 1, 1), + new("G", pixelType, 0, 1, 1), + new("R", pixelType, 0, 1, 1), + } + }; + + // Write magick bytes. + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Version number. + this.buffer[0] = 2; + + // Second, third and fourth bytes store info about the image, all set to zero. + this.buffer[1] = 0; + this.buffer[2] = 0; + this.buffer[3] = 0; + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write EXR header. + this.WriteHeader(stream, header); + + // Write pixel data. + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + + // Write offsets to each pixel row. + uint rowSizeBytes = (uint)(width * 3 * 4); + this.WriteRowOffsets(stream, height, rowSizeBytes); + for (int y = 0; y < height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + + for (int x = 0; x < width; x++) + { + var vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; + } + + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, redBuffer[x]); + } + } + } + + private void WriteHeader(Stream stream, ExrHeader header) + { + this.WriteChannels(stream, header.Channels); + this.WriteCompression(stream, header.Compression.Value); + this.WriteDataWindow(stream, header.DataWindow.Value); + this.WriteDisplayWindow(stream, header.DisplayWindow.Value); + this.WritePixelAspectRatio(stream, header.AspectRatio.Value); + this.WriteLineOrder(stream, header.LineOrder.Value); + this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); + this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); + stream.WriteByte(0); + } + + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + { + ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); + ulong offset = startOfPixelData; + for (int i = 0; i < height; i++) + { + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); + stream.Write(this.buffer); + offset += 4 + 4 + rowSizeBytes; + } + } + + private void WriteChannels(Stream stream, IList channels) + { + int attributeSize = 0; + foreach (ExrChannelInfo channelInfo in channels) + { + attributeSize += channelInfo.ChannelName.Length + 1; + attributeSize += 16; + } + + // Last zero byte. + attributeSize++; + this.WriteAttributeInformation(stream, "channels", "chlist", attributeSize); + + foreach (ExrChannelInfo channelInfo in channels) + { + this.WriteChannelInfo(stream, channelInfo); + } + + // Last byte should be zero. + stream.WriteByte(0); + } + + private void WriteCompression(Stream stream, ExrCompression compression) + { + this.WriteAttributeInformation(stream, "compression", "compression", 1); + stream.WriteByte((byte)compression); + } + + private void WritePixelAspectRatio(Stream stream, float aspectRatio) + { + this.WriteAttributeInformation(stream, "pixelAspectRatio", "float", 4); + this.WriteSingle(stream, aspectRatio); + } + + private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + { + this.WriteAttributeInformation(stream, "lineOrder", "lineOrder", 1); + stream.WriteByte((byte)lineOrder); + } + + private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + { + this.WriteAttributeInformation(stream, "screenWindowCenter", "v2f", 8); + this.WriteSingle(stream, screenWindowCenter.X); + this.WriteSingle(stream, screenWindowCenter.Y); + } + + private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + { + this.WriteAttributeInformation(stream, "screenWindowWidth", "float", 4); + this.WriteSingle(stream, screenWindowWidth); + } + + private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + { + this.WriteAttributeInformation(stream, "dataWindow", "box2i", 16); + this.WriteBox2i(stream, dataWindow); + } + + private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + { + this.WriteAttributeInformation(stream, "displayWindow", "box2i", 16); + this.WriteBox2i(stream, displayWindow); + } + + private void WriteAttributeInformation(Stream stream, string name, string type, int size) + { + // Write attribute name. + this.WriteString(stream, name); + + // Write attribute type. + this.WriteString(stream, type); + + // Write attribute size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) + { + this.WriteString(stream, channelInfo.ChannelName); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); + stream.Write(this.buffer.AsSpan(0, 4)); + + stream.WriteByte(channelInfo.PLinear); + + // Next 3 bytes are reserved and will set to zero. + stream.WriteByte(0); + stream.WriteByte(0); + stream.WriteByte(0); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteString(Stream stream, string str) + { + foreach (char c in str) + { + stream.WriteByte((byte)c); + } + + // Write termination byte. + stream.WriteByte(0); + } + + private void WriteBox2i(Stream stream, ExrBox2i box) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private unsafe void WriteSingle(Stream stream, float value) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); + stream.Write(this.buffer.AsSpan(0, 4)); + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index f3723c542..1e6eaf2bb 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -19,9 +19,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private ExrMetadata(ExrMetadata other) - { - } + private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; + + /// + /// Gets or sets the pixel format. + /// + public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; /// public IDeepCloneable DeepClone() => new ExrMetadata(this); diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs index fc3dc8ef9..2a57afe98 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -3,7 +3,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { - internal enum ExrPixelType + /// + /// The different pixel formats for a OpenEXR image. + /// + public enum ExrPixelType { /// /// unsigned int (32 bit). diff --git a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs new file mode 100644 index 000000000..938236b69 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + /// + /// Configuration options for use during OpenExr encoding. + /// + internal interface IExrEncoderOptions + { + /// + /// Gets the pixel type of the image. + /// + ExrPixelType? PixelType { get; } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs new file mode 100644 index 000000000..24508f06a --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the open exr format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 94051e263..3d4298d80 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { @@ -13,8 +14,29 @@ namespace SixLabors.ImageSharp.PixelFormats /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. /// /// + [StructLayout(LayoutKind.Sequential)] public partial struct HalfVector4 : IPixel, IPackedVector { + /// + /// Gets or sets the red component. + /// + public float R; + + /// + /// Gets or sets the green component. + /// + public float G; + + /// + /// Gets or sets the blue component. + /// + public float B; + + /// + /// Gets or sets the alpha component. + /// + public float A; + /// /// Initializes a new instance of the struct. /// @@ -23,18 +45,43 @@ namespace SixLabors.ImageSharp.PixelFormats /// The z-component. /// The w-component. public HalfVector4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) { + this.R = x; + this.G = y; + this.B = z; + this.A = w; } /// /// Initializes a new instance of the struct. /// /// A vector containing the initial values for the components - public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + public HalfVector4(Vector4 vector) + { + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.A = vector.W; + } + + /// + /// Gets or sets the packed representation of the HalfVector4 struct. + /// + public ulong HalfVector + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } /// - public ulong PackedValue { get; set; } + public ulong PackedValue + { + readonly get => this.HalfVector; + set => this.HalfVector = value; + } /// /// Compares two objects for equality. @@ -86,11 +133,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - 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))); + public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); /// [MethodImpl(InliningOptions.ShortMethod)] From 4cf95a52df91bdf84c2bce158f1616fa29f1d87b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 1 Jan 2022 16:32:46 +0100 Subject: [PATCH 006/240] Encode EXR image with HalfSingle pixel data --- .../Formats/ImageExtensions.Save.cs | 25 ++--- src/ImageSharp/Formats/OpenExr/ExrBox2i.cs | 3 + .../Formats/OpenExr/ExrConstants.cs | 37 +++++++ .../Formats/OpenExr/ExrDecoderCore.cs | 16 +-- .../Formats/OpenExr/ExrEncoderCore.cs | 103 ++++++++++++------ src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 2 +- .../PixelImplementations/HalfVector4.cs | 59 ++-------- 7 files changed, 136 insertions(+), 109 deletions(-) diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 315b942c6..55fd00cf9 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -848,14 +848,13 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), cancellationToken); - // foo /// /// Saves the image to the given stream with the Open Exr format. /// /// The image this method extends. /// The file path to save the image to. /// Thrown if the path is null. - public static void SaveAsExr(this Image source, string path) => SaveAsExr(source, path, null); + public static void SaveAsOpenExr(this Image source, string path) => SaveAsOpenExr(source, path, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -864,7 +863,7 @@ namespace SixLabors.ImageSharp /// The file path to save the image to. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsExrAsync(this Image source, string path) => SaveAsExrAsync(source, path, null); + public static Task SaveAsOpenExrAsync(this Image source, string path) => SaveAsOpenExrAsync(source, path, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -874,8 +873,8 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsExrAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsExrAsync(source, path, null, cancellationToken); + public static Task SaveAsOpenExrAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsOpenExrAsync(source, path, null, cancellationToken); /// /// Saves the image to the given stream with the Open Exr format. @@ -884,7 +883,7 @@ namespace SixLabors.ImageSharp /// The file path to save the image to. /// The encoder to save the image with. /// Thrown if the path is null. - public static void SaveAsExr(this Image source, string path, ExrEncoder encoder) => + public static void SaveAsOpenExr(this Image source, string path, ExrEncoder encoder) => source.Save( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); @@ -898,7 +897,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) => + public static Task SaveAsOpenExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), @@ -910,8 +909,8 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The stream to save the image to. /// Thrown if the stream is null. - public static void SaveAsExr(this Image source, Stream stream) - => SaveAsExr(source, stream, null); + public static void SaveAsOpenExr(this Image source, Stream stream) + => SaveAsOpenExr(source, stream, null); /// /// Saves the image to the given stream with the Open Exr format. @@ -921,8 +920,8 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsExrAsync(source, stream, null, cancellationToken); + public static Task SaveAsOpenExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsOpenExrAsync(source, stream, null, cancellationToken); /// /// Saves the image to the given stream with the Open Exr format. @@ -932,7 +931,7 @@ namespace SixLabors.ImageSharp /// The encoder to save the image with. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static void SaveAsExr(this Image source, Stream stream, ExrEncoder encoder) + public static void SaveAsOpenExr(this Image source, Stream stream, ExrEncoder encoder) => source.Save( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); @@ -946,7 +945,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => + public static Task SaveAsOpenExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs index 6fd610026..89424b425 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.OpenExr { + [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] internal struct ExrBox2i { public ExrBox2i(int xMin, int yMin, int xMax, int yMax) diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index b3f1a3424..40c34cce2 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -24,5 +24,42 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// The magick bytes identifying an OpenExr image. /// public static readonly int MagickBytes = 20000630; + + /// + /// EXR attribute names. + /// + internal static class AttributeNames + { + public const string Channels = "channels"; + + public const string Compression = "compression"; + + public const string DataWindow = "dataWindow"; + + public const string DisplayWindow = "displayWindow"; + + public const string LineOrder = "lineOrder"; + + public const string PixelAspectRatio = "pixelAspectRatio"; + + public const string ScreenWindowCenter = "screenWindowCenter"; + + public const string ScreenWindowWidth = "screenWindowWidth"; + } + + internal static class AttibuteTypes + { + public const string ChannelList = "chlist"; + + public const string Compression = "compression"; + + public const string Float = "float"; + + public const string LineOrder = "lineOrder"; + + public const string TwoFloat = "v2f"; + + public const string BoxInt = "box2i"; + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index b0424857c..46d3941f3 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -381,35 +381,35 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { switch (attribute.Name) { - case "channels": + case ExrConstants.AttributeNames.Channels: IList channels = this.ReadChannelList(stream, attribute.Length); header.Channels = channels; break; - case "compression": + case ExrConstants.AttributeNames.Compression: header.Compression = (ExrCompression)stream.ReadByte(); break; - case "dataWindow": + case ExrConstants.AttributeNames.DataWindow: ExrBox2i dataWindow = this.ReadBox2i(stream); header.DataWindow = dataWindow; break; - case "displayWindow": + case ExrConstants.AttributeNames.DisplayWindow: ExrBox2i displayWindow = this.ReadBox2i(stream); header.DisplayWindow = displayWindow; break; - case "lineOrder": + case ExrConstants.AttributeNames.LineOrder: var lineOrder = (ExrLineOrder)stream.ReadByte(); header.LineOrder = lineOrder; break; - case "pixelAspectRatio": + case ExrConstants.AttributeNames.PixelAspectRatio: float aspectRatio = stream.ReadSingle(this.buffer); header.AspectRatio = aspectRatio; break; - case "screenWindowCenter": + case ExrConstants.AttributeNames.ScreenWindowCenter: float screenWindowCenterX = stream.ReadSingle(this.buffer); float screenWindowCenterY = stream.ReadSingle(this.buffer); header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); break; - case "screenWindowWidth": + case ExrConstants.AttributeNames.ScreenWindowWidth: float screenWindowWidth = stream.ReadSingle(this.buffer); header.ScreenWindowWidth = screenWindowWidth; break; diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 35b381e9b..e3c437670 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -7,7 +7,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Threading; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -29,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// private readonly MemoryAllocator memoryAllocator; - /// - /// The global configuration. - /// - private Configuration configuration; - /// /// The pixel type of the image. /// @@ -63,14 +57,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; ExrMetadata exrMetadata = metadata.GetExrMetadata(); this.pixelType ??= exrMetadata.PixelType; - int width = image.Width; int height = image.Height; - ExrPixelType pixelType = ExrPixelType.Float; var header = new ExrHeader() { Compression = ExrCompression.None, @@ -82,9 +73,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr ScreenWindowWidth = 1, Channels = new List() { - new("B", pixelType, 0, 1, 1), - new("G", pixelType, 0, 1, 1), - new("R", pixelType, 0, 1, 1), + new("B", this.pixelType.Value, 0, 1, 1), + new("G", this.pixelType.Value, 0, 1, 1), + new("R", this.pixelType.Value, 0, 1, 1), } }; @@ -112,7 +103,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Span greenBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); // Write offsets to each pixel row. - uint rowSizeBytes = (uint)(width * 3 * 4); + int bytesPerPixel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerPixel); this.WriteRowOffsets(stream, height, rowSizeBytes); for (int y = 0; y < height; y++) { @@ -134,19 +127,14 @@ namespace SixLabors.ImageSharp.Formats.OpenExr BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); stream.Write(this.buffer.AsSpan(0, 4)); - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, blueBuffer[x]); - } - - for (int x = 0; x < width; x++) + switch (this.pixelType) { - this.WriteSingle(stream, greenBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, redBuffer[x]); + case ExrPixelType.Float: + this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; } } } @@ -164,6 +152,42 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.WriteByte(0); } + private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, redBuffer[x]); + } + } + + private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(stream, redBuffer[x]); + } + } + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) { ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); @@ -187,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Last zero byte. attributeSize++; - this.WriteAttributeInformation(stream, "channels", "chlist", attributeSize); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); foreach (ExrChannelInfo channelInfo in channels) { @@ -200,45 +224,45 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void WriteCompression(Stream stream, ExrCompression compression) { - this.WriteAttributeInformation(stream, "compression", "compression", 1); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); } private void WritePixelAspectRatio(Stream stream, float aspectRatio) { - this.WriteAttributeInformation(stream, "pixelAspectRatio", "float", 4); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, aspectRatio); } private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) { - this.WriteAttributeInformation(stream, "lineOrder", "lineOrder", 1); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); stream.WriteByte((byte)lineOrder); } private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) { - this.WriteAttributeInformation(stream, "screenWindowCenter", "v2f", 8); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); this.WriteSingle(stream, screenWindowCenter.X); this.WriteSingle(stream, screenWindowCenter.Y); } private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) { - this.WriteAttributeInformation(stream, "screenWindowWidth", "float", 4); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, screenWindowWidth); } private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) { - this.WriteAttributeInformation(stream, "dataWindow", "box2i", 16); - this.WriteBox2i(stream, dataWindow); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, dataWindow); } private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) { - this.WriteAttributeInformation(stream, "displayWindow", "box2i", 16); - this.WriteBox2i(stream, displayWindow); + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, displayWindow); } private void WriteAttributeInformation(Stream stream, string name, string type, int size) @@ -286,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.WriteByte(0); } - private void WriteBox2i(Stream stream, ExrBox2i box) + private void WriteBoxInteger(Stream stream, ExrBox2i box) { BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); stream.Write(this.buffer.AsSpan(0, 4)); @@ -306,5 +330,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); stream.Write(this.buffer.AsSpan(0, 4)); } + + private void WriteHalfSingle(Stream stream, float value) + { + ushort valueAsShort = HalfTypeHelper.Pack(value); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + stream.Write(this.buffer.AsSpan(0, 2)); + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index 1e6eaf2bb..7af01a38f 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// /// Gets or sets the pixel format. /// - public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; + public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; /// public IDeepCloneable DeepClone() => new ExrMetadata(this); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 3d4298d80..94051e263 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -4,7 +4,6 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { @@ -14,29 +13,8 @@ namespace SixLabors.ImageSharp.PixelFormats /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. /// /// - [StructLayout(LayoutKind.Sequential)] public partial struct HalfVector4 : IPixel, IPackedVector { - /// - /// Gets or sets the red component. - /// - public float R; - - /// - /// Gets or sets the green component. - /// - public float G; - - /// - /// Gets or sets the blue component. - /// - public float B; - - /// - /// Gets or sets the alpha component. - /// - public float A; - /// /// Initializes a new instance of the struct. /// @@ -45,43 +23,18 @@ namespace SixLabors.ImageSharp.PixelFormats /// The z-component. /// The w-component. public HalfVector4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) { - this.R = x; - this.G = y; - this.B = z; - this.A = w; } /// /// Initializes a new instance of the struct. /// /// A vector containing the initial values for the components - public HalfVector4(Vector4 vector) - { - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - this.A = vector.W; - } - - /// - /// Gets or sets the packed representation of the HalfVector4 struct. - /// - public ulong HalfVector - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } + public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); /// - public ulong PackedValue - { - readonly get => this.HalfVector; - set => this.HalfVector = value; - } + public ulong PackedValue { get; set; } /// /// Compares two objects for equality. @@ -133,7 +86,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); + 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(InliningOptions.ShortMethod)] From 7e7472d575d6510b3d8cca1d7c4ebf115caa2d9a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 1 Jan 2022 18:09:31 +0100 Subject: [PATCH 007/240] Encode EXR image with usigned int pixel data --- .../Formats/OpenExr/ExrChannelInfo.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 14 +-- .../Formats/OpenExr/ExrEncoderCore.cs | 98 +++++++++++++++++-- src/ImageSharp/Image.Decode.cs | 2 - 4 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs index 81b40ea66..28a014853 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.OpenExr { + [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] [StructLayout(LayoutKind.Sequential, Pack = 1)] internal readonly struct ExrChannelInfo { diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 46d3941f3..053adbd07 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -106,8 +106,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -193,8 +193,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); + Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -389,11 +389,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr header.Compression = (ExrCompression)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: - ExrBox2i dataWindow = this.ReadBox2i(stream); + ExrBox2i dataWindow = this.ReadBoxInteger(stream); header.DataWindow = dataWindow; break; case ExrConstants.AttributeNames.DisplayWindow: - ExrBox2i displayWindow = this.ReadBox2i(stream); + ExrBox2i displayWindow = this.ReadBoxInteger(stream); header.DisplayWindow = displayWindow; break; case ExrConstants.AttributeNames.LineOrder: @@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return new ExrAttribute(attributeName, attributeType, attributeSize); } - private ExrBox2i ReadBox2i(BufferedReadStream stream) + private ExrBox2i ReadBoxInteger(BufferedReadStream stream) { stream.Read(this.buffer, 0, 4); int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index e3c437670..221877a06 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -57,6 +58,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + ImageMetadata metadata = image.Metadata; ExrMetadata exrMetadata = metadata.GetExrMetadata(); this.pixelType ??= exrMetadata.PixelType; @@ -86,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Version number. this.buffer[0] = 2; - // Second, third and fourth bytes store info about the image, all set to zero. + // Second, third and fourth bytes store info about the image, set all to default: zero. this.buffer[1] = 0; this.buffer[2] = 0; this.buffer[3] = 0; @@ -95,18 +98,33 @@ namespace SixLabors.ImageSharp.Formats.OpenExr // Write EXR header. this.WriteHeader(stream, header); + // Write offsets to each pixel row. + int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); + this.WriteRowOffsets(stream, height, rowSizeBytes); + // Write pixel data. - Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; + switch (this.pixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); + break; + case ExrPixelType.UnsignedInt: + this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); + break; + } + } + + private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); - Span blueBuffer = rgbBuffer.GetSpan().Slice(width, width); - Span greenBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - // Write offsets to each pixel row. - int bytesPerPixel = this.pixelType == ExrPixelType.Half ? 2 : 4; - int numberOfChannels = 3; - uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerPixel); - this.WriteRowOffsets(stream, height, rowSizeBytes); for (int y = 0; y < height; y++) { Span pixelRowSpan = pixels.DangerousGetRowSpan(y); @@ -139,6 +157,41 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + + var rgb = default(Rgb96); + for (int y = 0; y < height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + + for (int x = 0; x < width; x++) + { + var vector4 = pixelRowSpan[x].ToVector4(); + rgb.FromVector4(vector4); + + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; + } + + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); + } + } + private void WriteHeader(Stream stream, ExrHeader header) { this.WriteChannels(stream, header.Channels); @@ -188,6 +241,24 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, blueBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, greenBuffer[x]); + } + + for (int x = 0; x < width; x++) + { + this.WriteUnsignedInt(stream, redBuffer[x]); + } + } + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) { ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); @@ -325,17 +396,26 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] private unsafe void WriteSingle(Stream stream, float value) { BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] private void WriteHalfSingle(Stream stream, float value) { ushort valueAsShort = HalfTypeHelper.Pack(value); BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); stream.Write(this.buffer.AsSpan(0, 2)); } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteUnsignedInt(Stream stream, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); + stream.Write(this.buffer.AsSpan(0, 4)); + } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index ee340bf86..3e1ffe9ba 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; From 652b3855c40b5adde164dd1ec0d29f5fa4141cbc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 2 Jan 2022 18:03:19 +0100 Subject: [PATCH 008/240] Update tests for EXR images --- .../PixelImplementations/Rgb96.cs | 11 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 3 +- .../Formats/Exr/ImageExtensionsTest.cs | 156 ++++++++++++++++++ .../Formats/GeneralFormatTests.cs | 18 +- 4 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 405ed40a1..83333a377 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -18,7 +18,9 @@ namespace SixLabors.ImageSharp.PixelFormats [StructLayout(LayoutKind.Sequential)] public partial struct Rgb96 : IPixel { - private const float Max = uint.MaxValue; + private const float InvMax = 1.0f / uint.MaxValue; + + private const double Max = uint.MaxValue; /// /// Gets or sets the red component. @@ -86,6 +88,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromVector4(Vector4 vector) { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); this.R = (uint)(vector.X * Max); this.G = (uint)(vector.Y * Max); this.B = (uint)(vector.Z * Max); @@ -94,9 +97,9 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() => new( - this.R / Max, - this.G / Max, - this.B / Max, + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, 1.0f); /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 7497e1b4c..eda8626b1 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -8,7 +8,6 @@ using Moq; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.Memory; using Xunit; // ReSharper disable InconsistentNaming @@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 8; + private readonly int expectedDefaultConfigurationCount = 9; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs new file mode 100644 index 000000000..9ff506efa --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -0,0 +1,156 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr +{ + [Trait("Format", "Exr")] + public class ImageExtensionsTest + { + [Fact] + public void SaveAsExr_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExr_Path.exr"); + + using (var image = new Image(10, 10)) + { + image.SaveAsOpenExr(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsExrAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsExr_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); + + using (var image = new Image(10, 10)) + { + image.SaveAsOpenExr(file, new ExrEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsExrAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(file, new ExrEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsExr_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsOpenExr(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsExrAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsExr_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsOpenExr(memoryStream, new ExrEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsExrAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 5a8425c1f..7686f77d9 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -85,11 +85,11 @@ namespace SixLabors.ImageSharp.Tests.Formats public static readonly TheoryData QuantizerNames = new() { - nameof(KnownQuantizers.Octree), - nameof(KnownQuantizers.WebSafe), - nameof(KnownQuantizers.Werner), - nameof(KnownQuantizers.Wu) - }; + nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.WebSafe), + nameof(KnownQuantizers.Werner), + nameof(KnownQuantizers.Wu) + }; [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] @@ -156,6 +156,11 @@ namespace SixLabors.ImageSharp.Tests.Formats { image.SaveAsTiff(output); } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.exr"))) + { + image.SaveAsOpenExr(output); + } } } } @@ -210,6 +215,9 @@ namespace SixLabors.ImageSharp.Tests.Formats [InlineData(100, 100, "tiff")] [InlineData(100, 10, "tiff")] [InlineData(10, 100, "tiff")] + [InlineData(100, 100, "exr")] + [InlineData(100, 10, "exr")] + [InlineData(10, 100, "exr")] public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { From 70cfcc9904b10d81c13d5d17c3e09ac3d0cc16f4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 3 Jan 2022 12:47:40 +0100 Subject: [PATCH 009/240] Add support for decoding EXR images with alpha channel --- .../Formats/OpenExr/ExrCompression.cs | 53 ++++- .../Formats/OpenExr/ExrConstants.cs | 14 ++ .../Formats/OpenExr/ExrDecoderCore.cs | 123 +++++++++--- .../Formats/OpenExr/ExrEncoderCore.cs | 6 +- .../PixelImplementations/Rgb96.cs | 8 +- .../PixelImplementations/Rgba128.cs | 183 ++++++++++++++++++ 6 files changed, 357 insertions(+), 30 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs index bcc177bde..64df462ed 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrCompression.cs @@ -5,6 +5,57 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { internal enum ExrCompression { - None = 0 + /// + /// Pixel data is not compressed. + /// + None = 0, + + /// + /// Differences between horizontally adjacent pixels are run-length encoded. + /// This method is fast, and works well for images with large flat areas, but for photographic images, + /// the compressed file size is usually between 60 and 75 percent of the uncompressed size. + /// Compression is lossless. + /// + RunLengthEncoded = 1, + + /// + /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time. + /// Compression is lossless. + /// + Zips = 2, + + /// + /// Differences between horizontally adjacent pixels are compressed using the open source zlib library. + /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines. + /// Compression is lossless. + /// + Zip = 3, + + /// + /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded. + /// Compression is lossless. + /// + Piz = 4, + + /// + /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib, + /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5. + /// Compression is lossy. + /// + Pxr24 = 5, + + /// + /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes, + /// reducing the data to 44 percent of their uncompressed size. + /// Compression is lossy. + /// + B44 = 6, + + /// + /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes. + /// For images with large uniform areas, B44A produces smaller files than B44 compression. + /// Compression is lossy. + /// + B44A = 7 } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index 40c34cce2..079adec7e 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -47,6 +47,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public const string ScreenWindowWidth = "screenWindowWidth"; } + /// + /// EXR attribute types. + /// internal static class AttibuteTypes { public const string ChannelList = "chlist"; @@ -61,5 +64,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public const string BoxInt = "box2i"; } + + internal static class ChannelNames + { + public const string Red = "R"; + + public const string Green = "G"; + + public const string Blue = "B"; + + public const string Alpha = "A"; + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 053adbd07..4a86dbad8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -74,9 +74,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression != ExrCompression.None) + if (this.Compression is not ExrCompression.None) { - ExrThrowHelper.ThrowNotSupported("Only uncompressed EXR images are supported"); + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } ExrPixelType pixelType = this.ValidateChannels(); @@ -104,10 +104,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -125,12 +126,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Read(this.buffer, 0, 4); uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + bool hasAlpha = false; for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; switch (channel.ChannelName) { - case "R": + case ExrConstants.ChannelNames.Red: switch (channel.PixelType) { case ExrPixelType.Half: @@ -143,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; - case "B": + case ExrConstants.ChannelNames.Blue: switch (channel.PixelType) { case ExrPixelType.Half: @@ -156,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; - case "G": + case ExrConstants.ChannelNames.Green: switch (channel.PixelType) { case ExrPixelType.Half: @@ -169,6 +171,20 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; + case ExrConstants.ChannelNames.Alpha: + hasAlpha = true; + switch (channel.PixelType) + { + case ExrPixelType.Half: + this.ReadPixelRowChannelHalfSingle(stream, alphaPixelData); + break; + case ExrPixelType.Float: + this.ReadPixelRowChannelSingle(stream, alphaPixelData); + break; + } + + break; + default: // Skip unknown channel. int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; @@ -179,11 +195,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = nextRowOffsetPosition; - for (int x = 0; x < this.Width; x++) + if (hasAlpha) + { + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } + } + else { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } } @@ -191,10 +219,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 3); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); TPixel color = default; for (int y = 0; y < this.Height; y++) @@ -212,12 +241,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Read(this.buffer, 0, 4); uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + bool hasAlpha = false; for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; switch (channel.ChannelName) { - case "R": + case ExrConstants.ChannelNames.Red: switch (channel.PixelType) { case ExrPixelType.UnsignedInt: @@ -227,7 +257,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; - case "B": + case ExrConstants.ChannelNames.Blue: switch (channel.PixelType) { case ExrPixelType.UnsignedInt: @@ -237,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; - case "G": + case ExrConstants.ChannelNames.Green: switch (channel.PixelType) { case ExrPixelType.UnsignedInt: @@ -247,6 +277,17 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; + case ExrConstants.ChannelNames.Alpha: + hasAlpha = true; + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + this.ReadPixelRowChannelUnsignedInt(stream, alphaPixelData); + break; + } + + break; + default: // Skip unknown channel. int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; @@ -257,11 +298,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = nextRowOffsetPosition; - for (int x = 0; x < this.Width; x++) + if (hasAlpha) { - var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } } @@ -324,19 +377,45 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { if (this.Channels.Count == 0) { - ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data expected!"); + ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); + } + + // Find pixel the type of any channel which is R, G, B or A. + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) + { + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha)) + { + pixelType = this.Channels[i].PixelType; + break; + } } - ExrPixelType pixelType = this.Channels[0].PixelType; - for (int i = 1; i < this.Channels.Count; i++) + if (!pixelType.HasValue) { + ExrThrowHelper.ThrowInvalidImageContentException("Pixel channel data is unknown! Only R, G, B and A are supported."); + } + + for (int i = 0; i < this.Channels.Count; i++) + { + // Ignore channels which we cannot interpret. + if (!(this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red))) + { + continue; + } + if (pixelType != this.Channels[i].PixelType) { ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!"); } } - return pixelType; + return pixelType.Value; } private ExrHeader ReadExrHeader(BufferedReadStream stream) diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 221877a06..1890d0b2b 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -76,9 +76,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr ScreenWindowWidth = 1, Channels = new List() { - new("B", this.pixelType.Value, 0, 1, 1), - new("G", this.pixelType.Value, 0, 1, 1), - new("R", this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), } }; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 83333a377..2809a4c31 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.PixelFormats { /// /// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue + /// The color components are stored in red, green, blue. /// /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. /// @@ -23,17 +23,17 @@ namespace SixLabors.ImageSharp.PixelFormats private const double Max = uint.MaxValue; /// - /// Gets or sets the red component. + /// Gets the red component. /// public uint R; /// - /// Gets or sets the green component. + /// Gets the green component. /// public uint G; /// - /// Gets or sets the blue component. + /// Gets the blue component. /// public uint B; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs new file mode 100644 index 000000000..01fc16d45 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -0,0 +1,183 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. + /// The color components are stored in red, green, blue and alpha. + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Rgba128 : IPixel + { + private const float InvMax = 1.0f / uint.MaxValue; + + private const double Max = uint.MaxValue; + + /// + /// Gets the red component. + /// + public uint R; + + /// + /// Gets the green component. + /// + public uint G; + + /// + /// Gets the blue component. + /// + public uint B; + + /// + /// Gets the alpha channel. + /// + public uint A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba128(uint r, uint g, uint b, uint a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.R = (uint)(vector.X * Max); + this.G = (uint)(vector.Y * Max); + this.B = (uint)(vector.Z * Max); + this.A = (uint)(vector.W * Max); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new( + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, + this.A * InvMax); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + + /// + public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + } +} From 327976276814c82e9d22d8af95d243ed3f71899e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 21 Jan 2022 18:25:58 +0100 Subject: [PATCH 010/240] Add support for decoding exr with zips compression --- .../Compressors/NoneExrCompression.cs | 24 ++++ .../Compressors/ZipsExrCompression.cs | 75 +++++++++++ .../OpenExr/Compression/ExrBaseCompression.cs | 43 +++++++ .../Compression/ExrBaseDecompressor.cs | 19 +++ .../ExrCompressionType.cs} | 4 +- .../Compression/ExrDecompressorFactory.cs | 24 ++++ .../Formats/OpenExr/ExrDecoderCore.cs | 121 ++++++++++++++---- .../Formats/OpenExr/ExrEncoderCore.cs | 5 +- src/ImageSharp/Formats/OpenExr/ExrHeader.cs | 3 +- .../Formats/OpenExr/ExrThrowHelper.cs | 3 + 10 files changed, 288 insertions(+), 33 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs rename src/ImageSharp/Formats/OpenExr/{ExrCompression.cs => Compression/ExrCompressionType.cs} (96%) create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs new file mode 100644 index 000000000..d4eaf67eb --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +{ + internal class NoneExrCompression : ExrBaseDecompressor + { + public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) + { + } + + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); + + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs new file mode 100644 index 000000000..3f3db5327 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO.Compression; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +{ + internal class ZipsExrCompression : ExrBaseDecompressor + { + private readonly IMemoryOwner tmpBuffer; + + public ZipsExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + long pos = stream.Position; + using var deframeStream = new ZlibInflateStream( + stream, + () => + { + int left = (int)(compressedBytes - (stream.Position - pos)); + return left > 0 ? left : 0; + }); + deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); + DeflateStream dataStream = deframeStream.CompressedStream; + + Span tmp = this.tmpBuffer.GetSpan(); + int totalRead = 0; + while (totalRead < buffer.Length) + { + int bytesRead = dataStream.Read(tmp, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) + { + break; + } + + totalRead += bytesRead; + } + + Reconstruct(tmp, this.UncompressedBytes); + Interleave(tmp, this.UncompressedBytes, buffer); + } + + private static void Reconstruct(Span buffer, uint unCompressedBytes) + { + int offset = 0; + for (int i = 0; i < unCompressedBytes - 1; i++) + { + byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); + buffer[offset + 1] = d; + offset++; + } + } + + private static void Interleave(Span source, uint unCompressedBytes, Span output) + { + int sourceOffset = 0; + int offset0 = 0; + int offset1 = (int)((unCompressedBytes + 1) / 2); + while (sourceOffset < unCompressedBytes) + { + output[sourceOffset++] = source[offset0++]; + output[sourceOffset++] = source[offset1++]; + } + } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs new file mode 100644 index 000000000..7faf4e77d --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +{ + internal abstract class ExrBaseCompression : IDisposable + { + private bool isDisposed; + + protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) + { + this.Allocator = allocator; + this.UncompressedBytes = bytePerRow; + } + + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } + + /// + /// Gets the uncompressed bytes. + /// + public uint UncompressedBytes { get; } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.Dispose(true); + } + + protected abstract void Dispose(bool disposing); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs new file mode 100644 index 000000000..1eb31ff52 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +{ + internal abstract class ExrBaseDecompressor : ExrBaseCompression + { + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) + : base(allocator, bytePerRow) + { + } + + public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs similarity index 96% rename from src/ImageSharp/Formats/OpenExr/ExrCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs index 64df462ed..3b47ed6ec 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression { - internal enum ExrCompression + internal enum ExrCompressionType { /// /// Pixel data is not compressed. diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs new file mode 100644 index 000000000..7ce46b4af --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +{ + internal static class ExrDecompressorFactory + { + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint bytesPerRow) + { + switch (method) + { + case ExrCompressionType.None: + return new NoneExrCompression(memoryAllocator, bytesPerRow); + case ExrCompressionType.Zips: + return new ZipsExrCompression(memoryAllocator, bytesPerRow); + default: + throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); + } + } + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 4a86dbad8..b5cae391d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -5,8 +5,10 @@ using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -66,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private IList Channels { get; set; } - private ExrCompression Compression { get; set; } + private ExrCompressionType Compression { get; set; } /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -74,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression is not ExrCompression.None) + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips) { ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } @@ -104,12 +106,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow(); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + TPixel color = default; for (int y = 0; y < this.Height; y++) { @@ -124,9 +133,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); stream.Read(this.buffer, 0, 4); - uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - bool hasAlpha = false; + int offset = 0; for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; @@ -136,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.Half: - this.ReadPixelRowChannelHalfSingle(stream, redPixelData); + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); break; case ExrPixelType.Float: - this.ReadPixelRowChannelSingle(stream, redPixelData); + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); break; } @@ -149,10 +159,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.Half: - this.ReadPixelRowChannelHalfSingle(stream, bluePixelData); + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); break; case ExrPixelType.Float: - this.ReadPixelRowChannelSingle(stream, bluePixelData); + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); break; } @@ -162,24 +172,23 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.Half: - this.ReadPixelRowChannelHalfSingle(stream, greenPixelData); + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); break; case ExrPixelType.Float: - this.ReadPixelRowChannelSingle(stream, greenPixelData); + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); break; } break; case ExrConstants.ChannelNames.Alpha: - hasAlpha = true; switch (channel.PixelType) { case ExrPixelType.Half: - this.ReadPixelRowChannelHalfSingle(stream, alphaPixelData); + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); break; case ExrPixelType.Float: - this.ReadPixelRowChannelSingle(stream, alphaPixelData); + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); break; } @@ -219,12 +228,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow(); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + TPixel color = default; for (int y = 0; y < this.Height; y++) { @@ -239,9 +255,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); stream.Read(this.buffer, 0, 4); - uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - bool hasAlpha = false; + int offset = 0; for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; @@ -251,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.UnsignedInt: - this.ReadPixelRowChannelUnsignedInt(stream, redPixelData); + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); break; } @@ -261,7 +278,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.UnsignedInt: - this.ReadPixelRowChannelUnsignedInt(stream, bluePixelData); + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); break; } @@ -271,18 +288,17 @@ namespace SixLabors.ImageSharp.Formats.OpenExr switch (channel.PixelType) { case ExrPixelType.UnsignedInt: - this.ReadPixelRowChannelUnsignedInt(stream, greenPixelData); + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); break; } break; case ExrConstants.ChannelNames.Alpha: - hasAlpha = true; switch (channel.PixelType) { case ExrPixelType.UnsignedInt: - this.ReadPixelRowChannelUnsignedInt(stream, alphaPixelData); + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); break; } @@ -319,29 +335,44 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } - private void ReadPixelRowChannelHalfSingle(BufferedReadStream stream, Span channelData) + private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData) { + int offset = 0; + ushort shortValue = 0; for (int x = 0; x < this.Width; x++) { - channelData[x] = stream.ReadHalfSingle(this.buffer); + shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); + channelData[x] = HalfTypeHelper.Unpack(shortValue); + offset += 2; } + + return offset; } - private void ReadPixelRowChannelSingle(BufferedReadStream stream, Span channelData) + private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData) { + int offset = 0; + int intValue = 0; for (int x = 0; x < this.Width; x++) { - channelData[x] = stream.ReadSingle(this.buffer); + intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + channelData[x] = Unsafe.As(ref intValue); + offset += 4; } + + return offset; } - private void ReadPixelRowChannelUnsignedInt(BufferedReadStream stream, Span channelData) + private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData) { + int offset = 0; for (int x = 0; x < this.Width; x++) { - stream.Read(this.buffer, 0, 4); - channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + offset += 4; } + + return offset; } /// @@ -465,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr header.Channels = channels; break; case ExrConstants.AttributeNames.Compression: - header.Compression = (ExrCompression)stream.ReadByte(); + header.Compression = (ExrCompressionType)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: ExrBox2i dataWindow = this.ReadBoxInteger(stream); @@ -602,5 +633,39 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return str.ToString(); } + + private bool HasAlpha() + { + foreach (ExrChannelInfo channelInfo in this.Channels) + { + if (channelInfo.ChannelName.Equals("A")) + { + return true; + } + } + + return false; + } + + private uint CalculateBytesPerRow() + { + uint bytesPerRow = 0; + foreach (ExrChannelInfo channelInfo in this.Channels) + { + if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B")) + { + if (channelInfo.PixelType == ExrPixelType.Half) + { + bytesPerRow += 2 * (uint)this.Width; + } + else + { + bytesPerRow += 4 * (uint)this.Width; + } + } + } + + return bytesPerRow; + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 1890d0b2b..63c171c35 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; +using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -67,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr int height = image.Height; var header = new ExrHeader() { - Compression = ExrCompression.None, + Compression = ExrCompressionType.None, AspectRatio = 1.0f, DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), @@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.WriteByte(0); } - private void WriteCompression(Stream stream, ExrCompression compression) + private void WriteCompression(Stream stream, ExrCompressionType compression) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs index 1ed464ebd..a463abf72 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrHeader.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using SixLabors.ImageSharp.Formats.OpenExr.Compression; namespace SixLabors.ImageSharp.Formats.OpenExr { @@ -9,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { public IList Channels { get; set; } - public ExrCompression? Compression { get; set; } + public ExrCompressionType? Compression { get; set; } public ExrBox2i? DataWindow { get; set; } diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs index ae7654111..b68fb5a2f 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// internal static class ExrThrowHelper { + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); From 85501f3c007c48af54b54084a2245ab014ef94fc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 21 Jan 2022 21:38:44 +0100 Subject: [PATCH 011/240] Add support for decoding RLE exr images --- .../Compressors/RunLengthCompression.cs | 94 +++++++++++++++++++ .../Compressors/ZipsExrCompression.cs | 32 +------ .../Compression/ExrBaseDecompressor.cs | 23 +++++ .../Compression/ExrDecompressorFactory.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 3 +- 5 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs new file mode 100644 index 000000000..de1bf5232 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +{ + internal class RunLengthCompression : ExrBaseDecompressor + { + private readonly IMemoryOwner tmpBuffer; + + public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + int maxLength = (int)this.UncompressedBytes; + int offset = 0; + while (compressedBytes > 0) + { + byte nextByte = ReadNextByte(stream); + + sbyte input = (sbyte)nextByte; + if (input < 0) + { + int count = -input; + compressedBytes -= (uint)(count + 1); + + if ((maxLength -= count) < 0) + { + return; + } + + // Check the input buffer is big enough to contain 'count' bytes of remaining data. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count; i++) + { + uncompressed[offset + i] = ReadNextByte(stream); + } + + offset += count; + } + else + { + int count = input; + byte value = ReadNextByte(stream); + compressedBytes -= 2; + + if ((maxLength -= count + 1) < 0) + { + return; + } + + // Check the input buffer is big enough to contain byte to be duplicated. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count + 1; i++) + { + uncompressed[offset + i] = value; + } + + offset += count + 1; + } + } + + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); + } + + private static byte ReadNextByte(BufferedReadStream stream) + { + int nextByte = stream.ReadByte(); + if (nextByte == -1) + { + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); + } + + return (byte)nextByte; + } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs index 3f3db5327..162893b11 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { + Span uncompressed = this.tmpBuffer.GetSpan(); + long pos = stream.Position; using var deframeStream = new ZlibInflateStream( stream, @@ -30,11 +32,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); DeflateStream dataStream = deframeStream.CompressedStream; - Span tmp = this.tmpBuffer.GetSpan(); int totalRead = 0; while (totalRead < buffer.Length) { - int bytesRead = dataStream.Read(tmp, totalRead, buffer.Length - totalRead); + int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); if (bytesRead <= 0) { break; @@ -43,31 +44,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } - Reconstruct(tmp, this.UncompressedBytes); - Interleave(tmp, this.UncompressedBytes, buffer); - } - - private static void Reconstruct(Span buffer, uint unCompressedBytes) - { - int offset = 0; - for (int i = 0; i < unCompressedBytes - 1; i++) - { - byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); - buffer[offset + 1] = d; - offset++; - } - } - - private static void Interleave(Span source, uint unCompressedBytes, Span output) - { - int sourceOffset = 0; - int offset0 = 0; - int offset1 = (int)((unCompressedBytes + 1) / 2); - while (sourceOffset < unCompressedBytes) - { - output[sourceOffset++] = source[offset0++]; - output[sourceOffset++] = source[offset1++]; - } + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); } protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs index 1eb31ff52..f8b811e50 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs @@ -15,5 +15,28 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression } public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + + protected static void Reconstruct(Span buffer, uint unCompressedBytes) + { + int offset = 0; + for (int i = 0; i < unCompressedBytes - 1; i++) + { + byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); + buffer[offset + 1] = d; + offset++; + } + } + + protected static void Interleave(Span source, uint unCompressedBytes, Span output) + { + int sourceOffset = 0; + int offset0 = 0; + int offset1 = (int)((unCompressedBytes + 1) / 2); + while (sourceOffset < unCompressedBytes) + { + output[sourceOffset++] = source[offset0++]; + output[sourceOffset++] = source[offset1++]; + } + } } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index 7ce46b4af..ebbd3416f 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression return new NoneExrCompression(memoryAllocator, bytesPerRow); case ExrCompressionType.Zips: return new ZipsExrCompression(memoryAllocator, bytesPerRow); + case ExrCompressionType.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, bytesPerRow); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index b5cae391d..8674aa12c 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; +using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -76,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips) + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.RunLengthEncoded) { ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } From 69c8cd8a5f265e9cbcb64ca8e36689d516f73762 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 22 Jan 2022 19:11:56 +0100 Subject: [PATCH 012/240] Add support for ZIP compressed exr images --- ...ExrCompression.cs => ZipExrCompression.cs} | 8 +- .../Compression/ExrDecompressorFactory.cs | 10 +- .../Formats/OpenExr/ExrDecoderCore.cs | 336 ++++++++++-------- 3 files changed, 189 insertions(+), 165 deletions(-) rename src/ImageSharp/Formats/OpenExr/Compression/Compressors/{ZipsExrCompression.cs => ZipExrCompression.cs} (85%) diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs similarity index 85% rename from src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 162893b11..508a5b4ac 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipsExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -10,11 +10,11 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors { - internal class ZipsExrCompression : ExrBaseDecompressor + internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - public ZipsExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) @@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); + Reconstruct(uncompressed, (uint)totalRead); + Interleave(uncompressed, (uint)totalRead, buffer); } protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index ebbd3416f..8ba255d41 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -8,16 +8,18 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression { internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint bytesPerRow) + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) { switch (method) { case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, bytesPerRow); + return new NoneExrCompression(memoryAllocator, uncompressedBytes); case ExrCompressionType.Zips: - return new ZipsExrCompression(memoryAllocator, bytesPerRow); + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zip: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, bytesPerRow); + return new RunLengthCompression(memoryAllocator, uncompressedBytes); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 8674aa12c..aadbe425e 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -9,7 +9,6 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; -using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -77,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { this.ReadExrHeader(stream); - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.RunLengthEncoded) + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) { ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } @@ -109,19 +108,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { bool hasAlpha = this.HasAlpha(); uint bytesPerRow = this.CalculateBytesPerRow(); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (int y = 0; y < this.Height; y++) + for (uint y = 0; y < this.Height; y += rowsPerBlock) { stream.Read(this.buffer, 0, 8); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); @@ -129,100 +130,102 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = (long)rowOffset; stream.Read(this.buffer, 0, 4); - uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); stream.Read(this.buffer, 0, 4); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) { - ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Blue: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Green: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Alpha: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); + break; + } + + break; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + break; + } } - } - - stream.Position = nextRowOffsetPosition; - if (hasAlpha) - { - for (int x = 0; x < this.Width; x++) + if (hasAlpha) { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } - } - else - { - for (int x = 0; x < this.Width; x++) + else { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } + + stream.Position = nextRowOffsetPosition; } } @@ -231,19 +234,21 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { bool hasAlpha = this.HasAlpha(); uint bytesPerRow = this.CalculateBytesPerRow(); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerRow); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerRow); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (int y = 0; y < this.Height; y++) + for (uint y = 0; y < this.Height; y += rowsPerBlock) { stream.Read(this.buffer, 0, 8); ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); @@ -251,86 +256,88 @@ namespace SixLabors.ImageSharp.Formats.OpenExr stream.Position = (long)rowOffset; stream.Read(this.buffer, 0, 4); - uint rowIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); stream.Read(this.buffer, 0, 4); uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) { - ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; + ExrChannelInfo channel = this.Channels[channelIdx]; + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Blue: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Green: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); + break; + } + + break; + + case ExrConstants.ChannelNames.Alpha: + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); + break; + } + + break; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + break; + } } - } - stream.Position = nextRowOffsetPosition; + stream.Position = nextRowOffsetPosition; - if (hasAlpha) - { - for (int x = 0; x < this.Width; x++) + if (hasAlpha) { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } - } - else - { - for (int x = 0; x < this.Width; x++) + else { - var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + for (int x = 0; x < this.Width; x++) + { + var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; + } } } } @@ -339,10 +346,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData) { int offset = 0; - ushort shortValue = 0; for (int x = 0; x < this.Width; x++) { - shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); + ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); channelData[x] = HalfTypeHelper.Unpack(shortValue); offset += 2; } @@ -353,10 +359,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData) { int offset = 0; - int intValue = 0; for (int x = 0; x < this.Width; x++) { - intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); channelData[x] = Unsafe.As(ref intValue); offset += 4; } @@ -668,5 +673,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return bytesPerRow; } + + private uint RowsPerBlock() + { + switch (this.Compression) + { + case ExrCompressionType.Zip: + case ExrCompressionType.Pxr24: + return 16; + case ExrCompressionType.B44: + case ExrCompressionType.B44A: + case ExrCompressionType.Piz: + return 32; + + default: + return 1; + } + } } } From fdc7b61b4b2a6d73fd8a7032ba10c88425a23c64 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 23 Jan 2022 19:43:38 +0100 Subject: [PATCH 013/240] Add support for decoding gray exr images --- .../Compressors/ZipExrCompression.cs | 5 + .../Formats/OpenExr/ExrConstants.cs | 2 + .../Formats/OpenExr/ExrDecoderCore.cs | 161 +++++++++++++----- .../Formats/OpenExr/ExrImageType.cs | 16 ++ 4 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrImageType.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 508a5b4ac..65a2ea348 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -44,6 +44,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors totalRead += bytesRead; } + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); + } + Reconstruct(uncompressed, (uint)totalRead); Interleave(uncompressed, (uint)totalRead, buffer); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index 079adec7e..ad6b1763d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -74,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public const string Blue = "B"; public const string Alpha = "A"; + + public const string Luminance = "Y"; } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index aadbe425e..a41ceb076 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -70,18 +70,16 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private ExrCompressionType Compression { get; set; } + private ExrImageType ImageType { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { this.ReadExrHeader(stream); - - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) - { - ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); - } - + this.IsSupportedCompression(); ExrPixelType pixelType = this.ValidateChannels(); + this.ReadImageType(); var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -197,6 +195,22 @@ namespace SixLabors.ImageSharp.Formats.OpenExr break; + case ExrConstants.ChannelNames.Luminance: + switch (channel.PixelType) + { + case ExrPixelType.Half: + offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + case ExrPixelType.Float: + offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); + break; + } + + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + + break; + default: // Skip unknown channel. int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; @@ -418,41 +432,9 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } // Find pixel the type of any channel which is R, G, B or A. - ExrPixelType? pixelType = null; - for (int i = 0; i < this.Channels.Count; i++) - { - if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha)) - { - pixelType = this.Channels[i].PixelType; - break; - } - } - - if (!pixelType.HasValue) - { - ExrThrowHelper.ThrowInvalidImageContentException("Pixel channel data is unknown! Only R, G, B and A are supported."); - } + ExrPixelType pixelType = this.FindPixelType(); - for (int i = 0; i < this.Channels.Count; i++) - { - // Ignore channels which we cannot interpret. - if (!(this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red))) - { - continue; - } - - if (pixelType != this.Channels[i].PixelType) - { - ExrThrowHelper.ThrowInvalidImageContentException("All channels are expected to have the same pixel data!"); - } - } - - return pixelType.Value; + return pixelType; } private ExrHeader ReadExrHeader(BufferedReadStream stream) @@ -640,6 +622,103 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return str.ToString(); } + private ExrPixelType FindPixelType() + { + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) + { + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance)) + { + if (!pixelType.HasValue) + { + pixelType = this.Channels[i].PixelType; + } + else + { + if (pixelType != this.Channels[i].PixelType) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); + } + } + } + } + + if (!pixelType.HasValue) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); + } + + return pixelType.Value; + } + + private void IsSupportedCompression() + { + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) + { + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + } + } + + private void ReadImageType() + { + bool hasRedChannel = false; + bool hasGreenChannel = false; + bool hasBlueChannel = false; + bool hasAlphaChannel = false; + bool hasLuminance = false; + foreach (ExrChannelInfo channelInfo in this.Channels) + { + if (channelInfo.ChannelName.Equals("A")) + { + hasAlphaChannel = true; + } + + if (channelInfo.ChannelName.Equals("R")) + { + hasRedChannel = true; + } + + if (channelInfo.ChannelName.Equals("G")) + { + hasGreenChannel = true; + } + + if (channelInfo.ChannelName.Equals("B")) + { + hasBlueChannel = true; + } + + if (channelInfo.ChannelName.Equals("Y")) + { + hasLuminance = true; + } + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + { + this.ImageType = ExrImageType.Rgba; + return; + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel) + { + this.ImageType = ExrImageType.Rgb; + return; + } + + if (hasLuminance && this.Channels.Count == 1) + { + this.ImageType = ExrImageType.Gray; + return; + } + + ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); + } + private bool HasAlpha() { foreach (ExrChannelInfo channelInfo in this.Channels) @@ -658,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr uint bytesPerRow = 0; foreach (ExrChannelInfo channelInfo in this.Channels) { - if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B")) + if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) { if (channelInfo.PixelType == ExrPixelType.Half) { diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs new file mode 100644 index 000000000..e4e9c8a64 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrImageType + { + Unknown = 0, + + Rgb = 1, + + Rgba = 2, + + Gray = 3, + } +} From fd0c245439947ea0783209b4925b5a9a35eefaa6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 27 Jan 2022 15:17:31 +0100 Subject: [PATCH 014/240] Refactor --- .../Formats/OpenExr/ExrConstants.cs | 4 + .../Formats/OpenExr/ExrDecoderCore.cs | 456 +++++++++--------- .../Formats/OpenExr/ExrEncoderCore.cs | 4 +- .../{ExrHeader.cs => ExrHeaderAttributes.cs} | 8 +- .../Formats/OpenExr/ExrImageDataType.cs | 16 + .../Formats/OpenExr/ExrImageType.cs | 8 +- 6 files changed, 255 insertions(+), 241 deletions(-) rename src/ImageSharp/Formats/OpenExr/{ExrHeader.cs => ExrHeaderAttributes.cs} (89%) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index ad6b1763d..29adb7b88 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -45,6 +45,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public const string ScreenWindowCenter = "screenWindowCenter"; public const string ScreenWindowWidth = "screenWindowWidth"; + + public const string Tiles = "tiles"; + + public const string ChunkCount = "chunkCount"; } /// diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index a41ceb076..d414babf2 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -70,8 +70,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr private ExrCompressionType Compression { get; set; } + private ExrImageDataType ImageDataType { get; set; } + private ExrImageType ImageType { get; set; } + private ExrHeaderAttributes HeaderAttributes { get; set; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -79,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr this.ReadExrHeader(stream); this.IsSupportedCompression(); ExrPixelType pixelType = this.ValidateChannels(); - this.ReadImageType(); + this.ReadImageDataType(); var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -105,138 +109,51 @@ namespace SixLabors.ImageSharp.Formats.OpenExr where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); uint rowsPerBlock = this.RowsPerBlock(); uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (uint y = 0; y < this.Height; y += rowsPerBlock) + for (uint y = 0; y < height; y += rowsPerBlock) { - stream.Read(this.buffer, 0, 8); - ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; stream.Position = (long)rowOffset; - stream.Read(this.buffer, 0, 4); - uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint rowStartIndex = this.ReadUnsignedInteger(stream); - stream.Read(this.buffer, 0, 4); - uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Luminance: - switch (channel.PixelType) - { - case ExrPixelType.Half: - offset += this.ReadPixelRowChannelHalfSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - case ExrPixelType.Float: - offset += this.ReadPixelRowChannelSingle(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - redPixelData.CopyTo(bluePixelData); - redPixelData.CopyTo(greenPixelData); - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; - } + offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } - if (hasAlpha) + for (int x = 0; x < width; x++) { - for (int x = 0; x < this.Width; x++) - { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } - } - else - { - for (int x = 0; x < this.Width; x++) - { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } + } stream.Position = nextRowOffsetPosition; @@ -247,120 +164,161 @@ namespace SixLabors.ImageSharp.Formats.OpenExr where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); uint rowsPerBlock = this.RowsPerBlock(); uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(this.Width * 4); + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, this.Width); - Span greenPixelData = rowBuffer.GetSpan().Slice(this.Width, this.Width); - Span bluePixelData = rowBuffer.GetSpan().Slice(this.Width * 2, this.Width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(this.Width * 3, this.Width); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); TPixel color = default; - for (uint y = 0; y < this.Height; y += rowsPerBlock) + for (uint y = 0; y < height; y += rowsPerBlock) { - stream.Read(this.buffer, 0, 8); - ulong rowOffset = BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; stream.Position = (long)rowOffset; - stream.Read(this.buffer, 0, 4); - uint rowStartIndex = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint rowStartIndex = this.ReadUnsignedInteger(stream); - stream.Read(this.buffer, 0, 4); - uint compressedBytesCount = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < this.Height; rowIndex++) + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), redPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Blue: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), bluePixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Green: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), greenPixelData); - break; - } - - break; - - case ExrConstants.ChannelNames.Alpha: - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - offset += this.ReadPixelRowChannelUnsignedInt(decompressedPixelData.Slice(offset), alphaPixelData); - break; - } - - break; - - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - break; - } + offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } stream.Position = nextRowOffsetPosition; - if (hasAlpha) + for (int x = 0; x < width; x++) { - for (int x = 0; x < this.Width; x++) - { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], alphaPixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } - } - else - { - for (int x = 0; x < this.Width; x++) - { - var pixelValue = new Rgb96(redPixelData[x], greenPixelData[x], bluePixelData[x]); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } } } } - private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData) + private int ReadFloatChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + + return bytesRead; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += width * channelDataSizeInBytes; + return channelDataSizeInBytes; + } + } + + private int ReadUnsignedIntChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) + { + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + return bytesRead; + + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + return channelDataSizeInBytes; + } + } + + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) + { + case ExrPixelType.Half: + return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); + case ExrPixelType.Float: + return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); + } + + return 0; + } + + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); + } + + return 0; + } + + private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; - for (int x = 0; x < this.Width; x++) + for (int x = 0; x < width; x++) { ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); channelData[x] = HalfTypeHelper.Unpack(shortValue); @@ -370,10 +328,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return offset; } - private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData) + private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; - for (int x = 0; x < this.Width; x++) + for (int x = 0; x < width; x++) { int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); channelData[x] = Unsafe.As(ref intValue); @@ -383,10 +341,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return offset; } - private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData) + private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) { int offset = 0; - for (int x = 0; x < this.Width; x++) + for (int x = 0; x < width; x++) { channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); offset += 4; @@ -398,7 +356,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - ExrHeader header = this.ReadExrHeader(stream); + ExrHeaderAttributes header = this.ReadExrHeader(stream); int bitsPerPixel = this.CalculateBitsPerPixel(); @@ -437,10 +395,10 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return pixelType; } - private ExrHeader ReadExrHeader(BufferedReadStream stream) + private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) { - // Skip over the magick bytes. - stream.Read(this.buffer, 0, 4); + // Skip over the magick bytes, we already know its an EXR image. + stream.Skip(4); // Read version number. byte version = (byte)stream.ReadByte(); @@ -450,30 +408,35 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } // Next three bytes contain info's about the image. - // TODO: We ignore those for now. - stream.Read(this.buffer, 0, 3); + byte flagsByte0 = (byte)stream.ReadByte(); + byte flagsByte1 = (byte)stream.ReadByte(); + byte flagsByte2 = (byte)stream.ReadByte(); + if ((flagsByte0 & (1 << 1)) != 0) + { + this.ImageType = ExrImageType.Tiled; + } - ExrHeader header = this.ParseHeader(stream); + this.HeaderAttributes = this.ParseHeaderAttributes(stream); - if (!header.IsValid()) + if (!this.HeaderAttributes.IsValid()) { ExrThrowHelper.ThrowInvalidImageHeader(); } - this.Width = header.DataWindow.Value.XMax - header.DataWindow.Value.XMin + 1; - this.Height = header.DataWindow.Value.YMax - header.DataWindow.Value.YMin + 1; - this.Channels = header.Channels; - this.Compression = header.Compression.GetValueOrDefault(); + this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; + this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; + this.Channels = this.HeaderAttributes.Channels; + this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); this.metadata = new ImageMetadata(); - return header; + return this.HeaderAttributes; } - private ExrHeader ParseHeader(BufferedReadStream stream) + private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) { ExrAttribute attribute = this.ReadAttribute(stream); - var header = new ExrHeader(); + var header = new ExrHeaderAttributes(); while (!attribute.Equals(ExrAttribute.EmptyAttribute)) { @@ -511,6 +474,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr float screenWindowWidth = stream.ReadSingle(this.buffer); header.ScreenWindowWidth = screenWindowWidth; break; + case ExrConstants.AttributeNames.Tiles: + header.TileXSize = this.ReadUnsignedInteger(stream); + header.TileYSize = this.ReadUnsignedInteger(stream); + break; + case ExrConstants.AttributeNames.ChunkCount: + header.ChunkCount = this.ReadSignedInteger(stream); + break; default: // Skip unknown attribute bytes. stream.Skip(attribute.Length); @@ -533,25 +503,17 @@ namespace SixLabors.ImageSharp.Formats.OpenExr string attributeType = ReadString(stream); - stream.Read(this.buffer, 0, 4); - int attributeSize = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + int attributeSize = this.ReadSignedInteger(stream); return new ExrAttribute(attributeName, attributeType, attributeSize); } private ExrBox2i ReadBoxInteger(BufferedReadStream stream) { - stream.Read(this.buffer, 0, 4); - int xMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); - - stream.Read(this.buffer, 0, 4); - int yMin = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); - - stream.Read(this.buffer, 0, 4); - int xMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); - - stream.Read(this.buffer, 0, 4); - int yMax = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + int xMin = this.ReadSignedInteger(stream); + int yMin = this.ReadSignedInteger(stream); + int xMax = this.ReadSignedInteger(stream); + int yMax = this.ReadSignedInteger(stream); return new ExrBox2i(xMin, yMin, xMax, yMax); } @@ -577,9 +539,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr string channelName = ReadString(stream); bytesRead = channelName.Length + 1; - stream.Read(this.buffer, 0, 4); + var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); bytesRead += 4; - var pixelType = (ExrPixelType)BinaryPrimitives.ReadInt32LittleEndian(this.buffer); byte pLinear = (byte)stream.ReadByte(); byte reserved0 = (byte)stream.ReadByte(); @@ -587,13 +548,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr byte reserved2 = (byte)stream.ReadByte(); bytesRead += 4; - stream.Read(this.buffer, 0, 4); + int xSampling = this.ReadSignedInteger(stream); bytesRead += 4; - int xSampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); - stream.Read(this.buffer, 0, 4); + int ySampling = this.ReadSignedInteger(stream); bytesRead += 4; - int ySampling = BinaryPrimitives.ReadInt32LittleEndian(this.buffer); return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); } @@ -663,7 +622,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } - private void ReadImageType() + private void ReadImageDataType() { bool hasRedChannel = false; bool hasGreenChannel = false; @@ -700,19 +659,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) { - this.ImageType = ExrImageType.Rgba; + this.ImageDataType = ExrImageDataType.Rgba; return; } if (hasRedChannel && hasGreenChannel && hasBlueChannel) { - this.ImageType = ExrImageType.Rgb; + this.ImageDataType = ExrImageDataType.Rgb; return; } if (hasLuminance && this.Channels.Count == 1) { - this.ImageType = ExrImageType.Gray; + this.ImageDataType = ExrImageDataType.Gray; return; } @@ -732,7 +691,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return false; } - private uint CalculateBytesPerRow() + private uint CalculateBytesPerRow(uint width) { uint bytesPerRow = 0; foreach (ExrChannelInfo channelInfo in this.Channels) @@ -741,11 +700,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { if (channelInfo.PixelType == ExrPixelType.Half) { - bytesPerRow += 2 * (uint)this.Width; + bytesPerRow += 2 * width; } else { - bytesPerRow += 4 * (uint)this.Width; + bytesPerRow += 4 * width; } } } @@ -769,5 +728,38 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return 1; } } + + private ulong ReadUnsignedLong(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 8); + if (bytesRead != 8) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + } + + return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + } + + private uint ReadUnsignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + } + + private int ReadSignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + } + + return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 63c171c35..eb626dc1b 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr this.pixelType ??= exrMetadata.PixelType; int width = image.Width; int height = image.Height; - var header = new ExrHeader() + var header = new ExrHeaderAttributes() { Compression = ExrCompressionType.None, AspectRatio = 1.0f, @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } - private void WriteHeader(Stream stream, ExrHeader header) + private void WriteHeader(Stream stream, ExrHeaderAttributes header) { this.WriteChannels(stream, header.Channels); this.WriteCompression(stream, header.Compression.Value); diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs similarity index 89% rename from src/ImageSharp/Formats/OpenExr/ExrHeader.cs rename to src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs index a463abf72..76d14311a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeader.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.OpenExr.Compression; namespace SixLabors.ImageSharp.Formats.OpenExr { - internal class ExrHeader + internal class ExrHeaderAttributes { public IList Channels { get; set; } @@ -24,6 +24,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr public PointF? ScreenWindowCenter { get; set; } + public uint? TileXSize { get; set; } + + public uint? TileYSize { get; set; } + + public int? ChunkCount { get; set; } + public bool IsValid() { if (!this.Compression.HasValue) diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs new file mode 100644 index 000000000..d17c3ad61 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.OpenExr +{ + internal enum ExrImageDataType + { + Unknown = 0, + + Rgb = 1, + + Rgba = 2, + + Gray = 3, + } +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs index e4e9c8a64..e18ee363f 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs @@ -5,12 +5,8 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { internal enum ExrImageType { - Unknown = 0, + ScanLine = 0, - Rgb = 1, - - Rgba = 2, - - Gray = 3, + Tiled = 1 } } From ea1aaa79c41c28d2c1bd886e7203e3467755ff39 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 18 Sep 2022 14:21:56 +0200 Subject: [PATCH 015/240] Fix build errors --- .../Compressors/NoneExrCompression.cs | 24 +- .../Compressors/RunLengthCompression.cs | 136 +- .../Compressors/ZipExrCompression.cs | 74 +- .../OpenExr/Compression/ExrBaseCompression.cs | 56 +- .../Compression/ExrBaseDecompressor.cs | 50 +- .../OpenExr/Compression/ExrCompressionType.cs | 113 +- .../Compression/ExrDecompressorFactory.cs | 33 +- .../Formats/OpenExr/ExrAttribute.cs | 32 +- src/ImageSharp/Formats/OpenExr/ExrBox2i.cs | 31 +- .../Formats/OpenExr/ExrChannelInfo.cs | 37 +- .../Formats/OpenExr/ExrConfigurationModule.cs | 25 +- .../Formats/OpenExr/ExrConstants.cs | 105 +- src/ImageSharp/Formats/OpenExr/ExrDecoder.cs | 91 +- .../Formats/OpenExr/ExrDecoderCore.cs | 1136 ++++++++--------- .../Formats/OpenExr/ExrDecoderOptions.cs | 13 + src/ImageSharp/Formats/OpenExr/ExrEncoder.cs | 48 +- .../Formats/OpenExr/ExrEncoderCore.cs | 633 +++++---- src/ImageSharp/Formats/OpenExr/ExrFormat.cs | 48 +- .../Formats/OpenExr/ExrHeaderAttributes.cs | 114 +- .../Formats/OpenExr/ExrImageDataType.cs | 17 +- .../Formats/OpenExr/ExrImageFormatDetector.cs | 38 +- .../Formats/OpenExr/ExrImageType.cs | 13 +- .../Formats/OpenExr/ExrLineOrder.cs | 15 +- src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 43 +- .../Formats/OpenExr/ExrPixelType.cs | 35 +- .../Formats/OpenExr/ExrThrowHelper.cs | 40 +- .../Formats/OpenExr/IExrDecoderOptions.cs | 12 - .../Formats/OpenExr/IExrEncoderOptions.cs | 19 +- .../Formats/OpenExr/MetadataExtensions.cs | 23 +- src/ImageSharp/IO/BufferedReadStream.cs | 26 + .../Formats/Exr/ImageExtensionsTest.cs | 163 +-- .../Formats/GeneralFormatTests.cs | 5 - 32 files changed, 1558 insertions(+), 1690 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs index d4eaf67eb..333c453eb 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs @@ -1,24 +1,22 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class NoneExrCompression : ExrBaseDecompressor { - internal class NoneExrCompression : ExrBaseDecompressor + public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) { - public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) - { - } + } - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) - => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); - protected override void Dispose(bool disposing) - { - } + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs index de1bf5232..08126c4de 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class RunLengthCompression : ExrBaseDecompressor { - internal class RunLengthCompression : ExrBaseDecompressor - { - private readonly IMemoryOwner tmpBuffer; + private readonly IMemoryOwner tmpBuffer; - public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + int maxLength = (int)this.UncompressedBytes; + int offset = 0; + while (compressedBytes > 0) { - Span uncompressed = this.tmpBuffer.GetSpan(); - int maxLength = (int)this.UncompressedBytes; - int offset = 0; - while (compressedBytes > 0) + byte nextByte = ReadNextByte(stream); + + sbyte input = (sbyte)nextByte; + if (input < 0) { - byte nextByte = ReadNextByte(stream); + int count = -input; + compressedBytes -= (uint)(count + 1); - sbyte input = (sbyte)nextByte; - if (input < 0) + if ((maxLength -= count) < 0) { - int count = -input; - compressedBytes -= (uint)(count + 1); - - if ((maxLength -= count) < 0) - { - return; - } - - // Check the input buffer is big enough to contain 'count' bytes of remaining data. - if (compressedBytes < 0) - { - return; - } - - for (int i = 0; i < count; i++) - { - uncompressed[offset + i] = ReadNextByte(stream); - } - - offset += count; + return; } - else + + // Check the input buffer is big enough to contain 'count' bytes of remaining data. + if (compressedBytes < 0) { - int count = input; - byte value = ReadNextByte(stream); - compressedBytes -= 2; - - if ((maxLength -= count + 1) < 0) - { - return; - } - - // Check the input buffer is big enough to contain byte to be duplicated. - if (compressedBytes < 0) - { - return; - } - - for (int i = 0; i < count + 1; i++) - { - uncompressed[offset + i] = value; - } - - offset += count + 1; + return; } - } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); - } + for (int i = 0; i < count; i++) + { + uncompressed[offset + i] = ReadNextByte(stream); + } - private static byte ReadNextByte(BufferedReadStream stream) - { - int nextByte = stream.ReadByte(); - if (nextByte == -1) + offset += count; + } + else { - ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); + int count = input; + byte value = ReadNextByte(stream); + compressedBytes -= 2; + + if ((maxLength -= count + 1) < 0) + { + return; + } + + // Check the input buffer is big enough to contain byte to be duplicated. + if (compressedBytes < 0) + { + return; + } + + for (int i = 0; i < count + 1; i++) + { + uncompressed[offset + i] = value; + } + + offset += count + 1; } + } + + Reconstruct(uncompressed, this.UncompressedBytes); + Interleave(uncompressed, this.UncompressedBytes, buffer); + } - return (byte)nextByte; + private static byte ReadNextByte(BufferedReadStream stream) + { + int nextByte = stream.ReadByte(); + if (nextByte == -1) + { + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); } - protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + return (byte)nextByte; } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs index 65a2ea348..d90f684e6 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.IO.Compression; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; + +internal class ZipExrCompression : ExrBaseDecompressor { - internal class ZipExrCompression : ExrBaseDecompressor - { - private readonly IMemoryOwner tmpBuffer; + private readonly IMemoryOwner tmpBuffer; - public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) + : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); - public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + + long pos = stream.Position; + using ZlibInflateStream deframeStream = new( + stream, + () => + { + int left = (int)(compressedBytes - (stream.Position - pos)); + return left > 0 ? left : 0; + }); + deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); + DeflateStream dataStream = deframeStream.CompressedStream; + + int totalRead = 0; + while (totalRead < buffer.Length) { - Span uncompressed = this.tmpBuffer.GetSpan(); - - long pos = stream.Position; - using var deframeStream = new ZlibInflateStream( - stream, - () => - { - int left = (int)(compressedBytes - (stream.Position - pos)); - return left > 0 ? left : 0; - }); - deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); - DeflateStream dataStream = deframeStream.CompressedStream; - - int totalRead = 0; - while (totalRead < buffer.Length) + int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) { - int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; + break; } - if (totalRead == 0) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); - } + totalRead += bytesRead; + } - Reconstruct(uncompressed, (uint)totalRead); - Interleave(uncompressed, (uint)totalRead, buffer); + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); } - protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); + Reconstruct(uncompressed, (uint)totalRead); + Interleave(uncompressed, (uint)totalRead, buffer); } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs index 7faf4e77d..4fd5778cb 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs @@ -1,43 +1,41 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal abstract class ExrBaseCompression : IDisposable { - internal abstract class ExrBaseCompression : IDisposable - { - private bool isDisposed; + private bool isDisposed; - protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) - { - this.Allocator = allocator; - this.UncompressedBytes = bytePerRow; - } + protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) + { + this.Allocator = allocator; + this.UncompressedBytes = bytePerRow; + } - /// - /// Gets the memory allocator. - /// - protected MemoryAllocator Allocator { get; } + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } - /// - /// Gets the uncompressed bytes. - /// - public uint UncompressedBytes { get; } + /// + /// Gets the uncompressed bytes. + /// + public uint UncompressedBytes { get; } - /// - public void Dispose() + /// + public void Dispose() + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.Dispose(true); + return; } - protected abstract void Dispose(bool disposing); + this.isDisposed = true; + this.Dispose(true); } + + protected abstract void Dispose(bool disposing); } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs index f8b811e50..2af814eb1 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs @@ -1,42 +1,40 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal abstract class ExrBaseDecompressor : ExrBaseCompression { - internal abstract class ExrBaseDecompressor : ExrBaseCompression + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) + : base(allocator, bytePerRow) { - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) - : base(allocator, bytePerRow) - { - } + } - public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); - protected static void Reconstruct(Span buffer, uint unCompressedBytes) + protected static void Reconstruct(Span buffer, uint unCompressedBytes) + { + int offset = 0; + for (int i = 0; i < unCompressedBytes - 1; i++) { - int offset = 0; - for (int i = 0; i < unCompressedBytes - 1; i++) - { - byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); - buffer[offset + 1] = d; - offset++; - } + byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); + buffer[offset + 1] = d; + offset++; } + } - protected static void Interleave(Span source, uint unCompressedBytes, Span output) + protected static void Interleave(Span source, uint unCompressedBytes, Span output) + { + int sourceOffset = 0; + int offset0 = 0; + int offset1 = (int)((unCompressedBytes + 1) / 2); + while (sourceOffset < unCompressedBytes) { - int sourceOffset = 0; - int offset0 = 0; - int offset1 = (int)((unCompressedBytes + 1) / 2); - while (sourceOffset < unCompressedBytes) - { - output[sourceOffset++] = source[offset0++]; - output[sourceOffset++] = source[offset1++]; - } + output[sourceOffset++] = source[offset0++]; + output[sourceOffset++] = source[offset1++]; } } } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs index 3b47ed6ec..53fc2e2dc 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs @@ -1,61 +1,60 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal enum ExrCompressionType { - internal enum ExrCompressionType - { - /// - /// Pixel data is not compressed. - /// - None = 0, - - /// - /// Differences between horizontally adjacent pixels are run-length encoded. - /// This method is fast, and works well for images with large flat areas, but for photographic images, - /// the compressed file size is usually between 60 and 75 percent of the uncompressed size. - /// Compression is lossless. - /// - RunLengthEncoded = 1, - - /// - /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time. - /// Compression is lossless. - /// - Zips = 2, - - /// - /// Differences between horizontally adjacent pixels are compressed using the open source zlib library. - /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines. - /// Compression is lossless. - /// - Zip = 3, - - /// - /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded. - /// Compression is lossless. - /// - Piz = 4, - - /// - /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib, - /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5. - /// Compression is lossy. - /// - Pxr24 = 5, - - /// - /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes, - /// reducing the data to 44 percent of their uncompressed size. - /// Compression is lossy. - /// - B44 = 6, - - /// - /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes. - /// For images with large uniform areas, B44A produces smaller files than B44 compression. - /// Compression is lossy. - /// - B44A = 7 - } + /// + /// Pixel data is not compressed. + /// + None = 0, + + /// + /// Differences between horizontally adjacent pixels are run-length encoded. + /// This method is fast, and works well for images with large flat areas, but for photographic images, + /// the compressed file size is usually between 60 and 75 percent of the uncompressed size. + /// Compression is lossless. + /// + RunLengthEncoded = 1, + + /// + /// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time. + /// Compression is lossless. + /// + Zips = 2, + + /// + /// Differences between horizontally adjacent pixels are compressed using the open source zlib library. + /// Unlike ZIPS compression, this operates in in blocks of 16 scan lines. + /// Compression is lossless. + /// + Zip = 3, + + /// + /// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded. + /// Compression is lossless. + /// + Piz = 4, + + /// + /// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib, + /// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5. + /// Compression is lossy. + /// + Pxr24 = 5, + + /// + /// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes, + /// reducing the data to 44 percent of their uncompressed size. + /// Compression is lossy. + /// + B44 = 6, + + /// + /// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes. + /// For images with large uniform areas, B44A produces smaller files than B44 compression. + /// Compression is lossy. + /// + B44A = 7 } diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index 8ba255d41..2d01efd87 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; + +internal static class ExrDecompressorFactory { - internal static class ExrDecompressorFactory + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) + switch (method) { - switch (method) - { - case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zips: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zip: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, uncompressedBytes); - default: - throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); - } + case ExrCompressionType.None: + return new NoneExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zips: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.Zip: + return new ZipExrCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, uncompressedBytes); + default: + throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs index 66423f50d..ad5a8290d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs @@ -1,26 +1,26 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License.. using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] +internal class ExrAttribute { - [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] - internal class ExrAttribute - { - public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); + public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); - public ExrAttribute(string name, string type, int length) - { - this.Name = name; - this.Type = type; - this.Length = length; - } + public ExrAttribute(string name, string type, int length) + { + this.Name = name; + this.Type = type; + this.Length = length; + } - public string Name { get; } + public string Name { get; } - public string Type { get; } + public string Type { get; } - public int Length { get; } - } + public int Length { get; } } + diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs index 89424b425..1f5b0c0df 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] +internal struct ExrBox2i { - [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] - internal struct ExrBox2i + public ExrBox2i(int xMin, int yMin, int xMax, int yMax) { - public ExrBox2i(int xMin, int yMin, int xMax, int yMax) - { - this.XMin = xMin; - this.YMin = yMin; - this.XMax = xMax; - this.YMax = yMax; - } + this.XMin = xMin; + this.YMin = yMin; + this.XMax = xMax; + this.YMax = yMax; + } - public int XMin { get; } + public int XMin { get; } - public int YMin { get; } + public int YMin { get; } - public int XMax { get; } + public int XMax { get; } - public int YMax { get; } - } + public int YMax { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs index 28a014853..d39bc08a5 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Diagnostics; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +[DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct ExrChannelInfo { - [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct ExrChannelInfo + public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) { - public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) - { - this.ChannelName = channelName; - this.PixelType = pixelType; - this.PLinear = pLinear; - this.XSampling = xSampling; - this.YSampling = ySampling; - } + this.ChannelName = channelName; + this.PixelType = pixelType; + this.PLinear = pLinear; + this.XSampling = xSampling; + this.YSampling = ySampling; + } - public string ChannelName { get; } + public string ChannelName { get; } - public ExrPixelType PixelType { get; } + public ExrPixelType PixelType { get; } - public byte PLinear { get; } + public byte PLinear { get; } - public int XSampling { get; } + public int XSampling { get; } - public int YSampling { get; } - } + public int YSampling { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs index d5a03cd5d..0b0058805 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Registers the image encoders, decoders and mime type detectors for the OpenExr format. +/// +public sealed class ExrConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. - /// - public sealed class ExrConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); - configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); + configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs index 29adb7b88..42ccf27fb 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConstants.cs @@ -1,85 +1,82 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.OpenExr; -namespace SixLabors.ImageSharp.Formats.OpenExr +/// +/// Defines constants relating to OpenExr images. +/// +internal static class ExrConstants { /// - /// Defines constants relating to OpenExr images. + /// The list of mimetypes that equate to a OpenExr image. /// - internal static class ExrConstants - { - /// - /// The list of mimetypes that equate to a OpenExr image. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" }; + public static readonly IEnumerable MimeTypes = new[] { "image/x-exr" }; - /// - /// The list of file extensions that equate to a OpenExr image. - /// - public static readonly IEnumerable FileExtensions = new[] { "exr" }; + /// + /// The list of file extensions that equate to a OpenExr image. + /// + public static readonly IEnumerable FileExtensions = new[] { "exr" }; - /// - /// The magick bytes identifying an OpenExr image. - /// - public static readonly int MagickBytes = 20000630; + /// + /// The magick bytes identifying an OpenExr image. + /// + public static readonly int MagickBytes = 20000630; - /// - /// EXR attribute names. - /// - internal static class AttributeNames - { - public const string Channels = "channels"; + /// + /// EXR attribute names. + /// + internal static class AttributeNames + { + public const string Channels = "channels"; - public const string Compression = "compression"; + public const string Compression = "compression"; - public const string DataWindow = "dataWindow"; + public const string DataWindow = "dataWindow"; - public const string DisplayWindow = "displayWindow"; + public const string DisplayWindow = "displayWindow"; - public const string LineOrder = "lineOrder"; + public const string LineOrder = "lineOrder"; - public const string PixelAspectRatio = "pixelAspectRatio"; + public const string PixelAspectRatio = "pixelAspectRatio"; - public const string ScreenWindowCenter = "screenWindowCenter"; + public const string ScreenWindowCenter = "screenWindowCenter"; - public const string ScreenWindowWidth = "screenWindowWidth"; + public const string ScreenWindowWidth = "screenWindowWidth"; - public const string Tiles = "tiles"; + public const string Tiles = "tiles"; - public const string ChunkCount = "chunkCount"; - } + public const string ChunkCount = "chunkCount"; + } - /// - /// EXR attribute types. - /// - internal static class AttibuteTypes - { - public const string ChannelList = "chlist"; + /// + /// EXR attribute types. + /// + internal static class AttibuteTypes + { + public const string ChannelList = "chlist"; - public const string Compression = "compression"; + public const string Compression = "compression"; - public const string Float = "float"; + public const string Float = "float"; - public const string LineOrder = "lineOrder"; + public const string LineOrder = "lineOrder"; - public const string TwoFloat = "v2f"; + public const string TwoFloat = "v2f"; - public const string BoxInt = "box2i"; - } + public const string BoxInt = "box2i"; + } - internal static class ChannelNames - { - public const string Red = "R"; + internal static class ChannelNames + { + public const string Red = "R"; - public const string Green = "G"; + public const string Green = "G"; - public const string Blue = "B"; + public const string Blue = "B"; - public const string Alpha = "A"; + public const string Alpha = "A"; - public const string Luminance = "Y"; - } + public const string Luminance = "Y"; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs index 0c645d71e..dcbe80c27 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs @@ -1,61 +1,46 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image decoder for generating an image out of a OpenExr stream. +/// +public class ExrDecoder : IImageDecoderSpecialized { - /// - /// Image decoder for generating an image out of a OpenExr stream. - /// - public sealed class ExrDecoder : IImageDecoder, IExrDecoderOptions, IImageInfoDetector + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - public Image Decode(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new ExrDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); - } - - /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); - - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new ExrDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); - - /// - public IImageInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - return new ExrDecoderCore(configuration, this).Identify(configuration, stream); - } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new ExrDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new ExrDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + Image image = new ExrDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + ImageDecoderUtilities.Resize(options.GeneralOptions, image); + + return image; + } + + /// + Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index d414babf2..ca244c567 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -1,765 +1,759 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; -using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Performs the OpenExr decoding operation. +/// +internal sealed class ExrDecoderCore : IImageDecoderInternals { /// - /// Performs the OpenExr decoding operation. + /// Reusable buffer. /// - internal sealed class ExrDecoderCore : IImageDecoderInternals - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[8]; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; + private readonly byte[] buffer = new byte[8]; - /// - /// The bitmap decoder options. - /// - private readonly IExrDecoderOptions options; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - /// - /// The metadata. - /// - private ImageMetadata metadata; + /// + /// The global configuration. + /// + private readonly Configuration configuration; - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public ExrDecoderCore(Configuration configuration, IExrDecoderOptions options) - { - this.Configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; - } + /// + /// The metadata. + /// + private ImageMetadata metadata; - /// - public Configuration Configuration { get; } + /// + /// Initializes a new instance of the class. + /// + /// The options. + public ExrDecoderCore(ExrDecoderOptions options) + { + this.Options = options.GeneralOptions; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - /// Gets the dimensions of the image. - /// - public Size Dimensions => new(this.Width, this.Height); + /// + public DecoderOptions Options { get; } - private int Width { get; set; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new(this.Width, this.Height); - private int Height { get; set; } + private int Width { get; set; } - private IList Channels { get; set; } + private int Height { get; set; } - private ExrCompressionType Compression { get; set; } + private IList Channels { get; set; } - private ExrImageDataType ImageDataType { get; set; } + private ExrCompressionType Compression { get; set; } - private ExrImageType ImageType { get; set; } + private ExrImageDataType ImageDataType { get; set; } - private ExrHeaderAttributes HeaderAttributes { get; set; } + private ExrImageType ImageType { get; set; } - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - this.ReadExrHeader(stream); - this.IsSupportedCompression(); - ExrPixelType pixelType = this.ValidateChannels(); - this.ReadImageDataType(); + private ExrHeaderAttributes HeaderAttributes { get; set; } - var image = new Image(this.Configuration, this.Width, this.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ReadExrHeader(stream); + this.IsSupportedCompression(); + ExrPixelType pixelType = this.ValidateChannels(); + this.ReadImageDataType(); - switch (pixelType) - { - case ExrPixelType.Half: - case ExrPixelType.Float: - this.DecodeFloatingPointPixelData(stream, pixels); - break; - case ExrPixelType.UnsignedInt: - this.DecodeUnsignedIntPixelData(stream, pixels); - break; - default: - ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); - break; - } + Image image = new Image(this.configuration, this.Width, this.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); - return image; + switch (pixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.DecodeFloatingPointPixelData(stream, pixels); + break; + case ExrPixelType.UnsignedInt: + this.DecodeUnsignedIntPixelData(stream, pixels); + break; + default: + ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); + break; } - private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + return image; + } + + private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; + + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + + TPixel color = default; + for (uint y = 0; y < height; y += rowsPerBlock) { - bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int width = this.Width; - int height = this.Height; + ulong rowOffset = this.ReadUnsignedLong(stream); + long nextRowOffsetPosition = stream.Position; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); - Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); - Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); - Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + stream.Position = (long)rowOffset; + uint rowStartIndex = this.ReadUnsignedInteger(stream); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - TPixel color = default; - for (uint y = 0; y < height; y += rowsPerBlock) + int offset = 0; + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { - ulong rowOffset = this.ReadUnsignedLong(stream); - long nextRowOffsetPosition = stream.Position; - - stream.Position = (long)rowOffset; - uint rowStartIndex = this.ReadUnsignedInteger(stream); - - uint compressedBytesCount = this.ReadUnsignedInteger(stream); - decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - - int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) - { - ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); - } - - for (int x = 0; x < width; x++) - { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + ExrChannelInfo channel = this.Channels[channelIdx]; + offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + } + for (int x = 0; x < width; x++) + { + var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } - stream.Position = nextRowOffsetPosition; } + + stream.Position = nextRowOffsetPosition; } + } - private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = this.HasAlpha(); + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int width = this.Width; + int height = this.Height; + + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); + Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); + Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); + Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + + TPixel color = default; + for (uint y = 0; y < height; y += rowsPerBlock) { - bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int width = this.Width; - int height = this.Height; + ulong rowOffset = this.ReadUnsignedLong(stream); + long nextRowOffsetPosition = stream.Position; - using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); - using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); - Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); - Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); - Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); - Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); + stream.Position = (long)rowOffset; + uint rowStartIndex = this.ReadUnsignedInteger(stream); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + uint compressedBytesCount = this.ReadUnsignedInteger(stream); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - TPixel color = default; - for (uint y = 0; y < height; y += rowsPerBlock) + int offset = 0; + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) { - ulong rowOffset = this.ReadUnsignedLong(stream); - long nextRowOffsetPosition = stream.Position; - - stream.Position = (long)rowOffset; - uint rowStartIndex = this.ReadUnsignedInteger(stream); - - uint compressedBytesCount = this.ReadUnsignedInteger(stream); - decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); - - int offset = 0; - for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { - Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); - for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) - { - ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); - } + ExrChannelInfo channel = this.Channels[channelIdx]; + offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + } - stream.Position = nextRowOffsetPosition; + stream.Position = nextRowOffsetPosition; - for (int x = 0; x < width; x++) - { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - color.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; - } + for (int x = 0; x < width; x++) + { + var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[x] = color; } } } + } - private int ReadFloatChannelData( - BufferedReadStream stream, - ExrChannelInfo channel, - Span decompressedPixelData, - Span redPixelData, - Span greenPixelData, - Span bluePixelData, - Span alphaPixelData, - int width) + private int ReadFloatChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) { - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); - case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); - case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); - case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - redPixelData.CopyTo(bluePixelData); - redPixelData.CopyTo(greenPixelData); + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); - return bytesRead; + return bytesRead; - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += width * channelDataSizeInBytes; - return channelDataSizeInBytes; - } + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += width * channelDataSizeInBytes; + return channelDataSizeInBytes; } + } - private int ReadUnsignedIntChannelData( - BufferedReadStream stream, - ExrChannelInfo channel, - Span decompressedPixelData, - Span redPixelData, - Span greenPixelData, - Span bluePixelData, - Span alphaPixelData, - int width) + private int ReadUnsignedIntChannelData( + BufferedReadStream stream, + ExrChannelInfo channel, + Span decompressedPixelData, + Span redPixelData, + Span greenPixelData, + Span bluePixelData, + Span alphaPixelData, + int width) + { + switch (channel.ChannelName) { - switch (channel.ChannelName) - { - case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + case ExrConstants.ChannelNames.Red: + return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + case ExrConstants.ChannelNames.Blue: + return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); - case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + case ExrConstants.ChannelNames.Green: + return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); - case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + case ExrConstants.ChannelNames.Alpha: + return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); - case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); - redPixelData.CopyTo(bluePixelData); - redPixelData.CopyTo(greenPixelData); - return bytesRead; + case ExrConstants.ChannelNames.Luminance: + int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + redPixelData.CopyTo(bluePixelData); + redPixelData.CopyTo(greenPixelData); + return bytesRead; - default: - // Skip unknown channel. - int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; - stream.Position += this.Width * channelDataSizeInBytes; - return channelDataSizeInBytes; - } + default: + // Skip unknown channel. + int channelDataSizeInBytes = channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt ? 4 : 2; + stream.Position += this.Width * channelDataSizeInBytes; + return channelDataSizeInBytes; } + } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) { - switch (channel.PixelType) - { - case ExrPixelType.Half: - return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); - case ExrPixelType.Float: - return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); - } - - return 0; + case ExrPixelType.Half: + return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); + case ExrPixelType.Float: + return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) - { - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); - } + return 0; + } - return 0; + private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + { + switch (channel.PixelType) + { + case ExrPixelType.UnsignedInt: + return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); } - private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); - channelData[x] = HalfTypeHelper.Unpack(shortValue); - offset += 2; - } + return 0; + } - return offset; + private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + ushort shortValue = BinaryPrimitives.ReadUInt16LittleEndian(decompressedPixelData.Slice(offset, 2)); + channelData[x] = HalfTypeHelper.Unpack(shortValue); + offset += 2; } - private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); - channelData[x] = Unsafe.As(ref intValue); - offset += 4; - } + return offset; + } - return offset; + private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + int intValue = BinaryPrimitives.ReadInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + channelData[x] = Unsafe.As(ref intValue); + offset += 4; } - private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) - { - int offset = 0; - for (int x = 0; x < width; x++) - { - channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); - offset += 4; - } + return offset; + } - return offset; + private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + channelData[x] = BinaryPrimitives.ReadUInt32LittleEndian(decompressedPixelData.Slice(offset, 4)); + offset += 4; } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - ExrHeaderAttributes header = this.ReadExrHeader(stream); + return offset; + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + ExrHeaderAttributes header = this.ReadExrHeader(stream); - int bitsPerPixel = this.CalculateBitsPerPixel(); + int bitsPerPixel = this.CalculateBitsPerPixel(); - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); - } + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + } - private int CalculateBitsPerPixel() + private int CalculateBitsPerPixel() + { + int bitsPerPixel = 0; + for (int i = 0; i < this.Channels.Count; i++) { - int bitsPerPixel = 0; - for (int i = 0; i < this.Channels.Count; i++) + ExrChannelInfo channel = this.Channels[0]; + if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) { - ExrChannelInfo channel = this.Channels[0]; - if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) - { - bitsPerPixel += 32; - } - else if (channel.PixelType == ExrPixelType.Half) - { - bitsPerPixel += 16; - } + bitsPerPixel += 32; } - - return bitsPerPixel; - } - - private ExrPixelType ValidateChannels() - { - if (this.Channels.Count == 0) + else if (channel.PixelType == ExrPixelType.Half) { - ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); + bitsPerPixel += 16; } - - // Find pixel the type of any channel which is R, G, B or A. - ExrPixelType pixelType = this.FindPixelType(); - - return pixelType; } - private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) + return bitsPerPixel; + } + + private ExrPixelType ValidateChannels() + { + if (this.Channels.Count == 0) { - // Skip over the magick bytes, we already know its an EXR image. - stream.Skip(4); + ExrThrowHelper.ThrowInvalidImageContentException("At least one channel of pixel data is expected!"); + } - // Read version number. - byte version = (byte)stream.ReadByte(); - if (version != 2) - { - ExrThrowHelper.ThrowNotSupportedVersion(); - } + // Find pixel the type of any channel which is R, G, B or A. + ExrPixelType pixelType = this.FindPixelType(); - // Next three bytes contain info's about the image. - byte flagsByte0 = (byte)stream.ReadByte(); - byte flagsByte1 = (byte)stream.ReadByte(); - byte flagsByte2 = (byte)stream.ReadByte(); - if ((flagsByte0 & (1 << 1)) != 0) - { - this.ImageType = ExrImageType.Tiled; - } + return pixelType; + } - this.HeaderAttributes = this.ParseHeaderAttributes(stream); + private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) + { + // Skip over the magick bytes, we already know its an EXR image. + stream.Skip(4); - if (!this.HeaderAttributes.IsValid()) - { - ExrThrowHelper.ThrowInvalidImageHeader(); - } + // Read version number. + byte version = (byte)stream.ReadByte(); + if (version != 2) + { + ExrThrowHelper.ThrowNotSupportedVersion(); + } - this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; - this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; - this.Channels = this.HeaderAttributes.Channels; - this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); + // Next three bytes contain info's about the image. + byte flagsByte0 = (byte)stream.ReadByte(); + byte flagsByte1 = (byte)stream.ReadByte(); + byte flagsByte2 = (byte)stream.ReadByte(); + if ((flagsByte0 & (1 << 1)) != 0) + { + this.ImageType = ExrImageType.Tiled; + } - this.metadata = new ImageMetadata(); + this.HeaderAttributes = this.ParseHeaderAttributes(stream); - return this.HeaderAttributes; + if (!this.HeaderAttributes.IsValid()) + { + ExrThrowHelper.ThrowInvalidImageHeader(); } - private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) - { - ExrAttribute attribute = this.ReadAttribute(stream); - var header = new ExrHeaderAttributes(); + this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; + this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; + this.Channels = this.HeaderAttributes.Channels; + this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); - while (!attribute.Equals(ExrAttribute.EmptyAttribute)) - { - switch (attribute.Name) - { - case ExrConstants.AttributeNames.Channels: - IList channels = this.ReadChannelList(stream, attribute.Length); - header.Channels = channels; - break; - case ExrConstants.AttributeNames.Compression: - header.Compression = (ExrCompressionType)stream.ReadByte(); - break; - case ExrConstants.AttributeNames.DataWindow: - ExrBox2i dataWindow = this.ReadBoxInteger(stream); - header.DataWindow = dataWindow; - break; - case ExrConstants.AttributeNames.DisplayWindow: - ExrBox2i displayWindow = this.ReadBoxInteger(stream); - header.DisplayWindow = displayWindow; - break; - case ExrConstants.AttributeNames.LineOrder: - var lineOrder = (ExrLineOrder)stream.ReadByte(); - header.LineOrder = lineOrder; - break; - case ExrConstants.AttributeNames.PixelAspectRatio: - float aspectRatio = stream.ReadSingle(this.buffer); - header.AspectRatio = aspectRatio; - break; - case ExrConstants.AttributeNames.ScreenWindowCenter: - float screenWindowCenterX = stream.ReadSingle(this.buffer); - float screenWindowCenterY = stream.ReadSingle(this.buffer); - header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); - break; - case ExrConstants.AttributeNames.ScreenWindowWidth: - float screenWindowWidth = stream.ReadSingle(this.buffer); - header.ScreenWindowWidth = screenWindowWidth; - break; - case ExrConstants.AttributeNames.Tiles: - header.TileXSize = this.ReadUnsignedInteger(stream); - header.TileYSize = this.ReadUnsignedInteger(stream); - break; - case ExrConstants.AttributeNames.ChunkCount: - header.ChunkCount = this.ReadSignedInteger(stream); - break; - default: - // Skip unknown attribute bytes. - stream.Skip(attribute.Length); - break; - } + this.metadata = new ImageMetadata(); - attribute = this.ReadAttribute(stream); - } + return this.HeaderAttributes; + } - return header; - } + private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) + { + ExrAttribute attribute = this.ReadAttribute(stream); + var header = new ExrHeaderAttributes(); - private ExrAttribute ReadAttribute(BufferedReadStream stream) + while (!attribute.Equals(ExrAttribute.EmptyAttribute)) { - string attributeName = ReadString(stream); - if (attributeName.Equals(string.Empty)) + switch (attribute.Name) { - return ExrAttribute.EmptyAttribute; + case ExrConstants.AttributeNames.Channels: + IList channels = this.ReadChannelList(stream, attribute.Length); + header.Channels = channels; + break; + case ExrConstants.AttributeNames.Compression: + header.Compression = (ExrCompressionType)stream.ReadByte(); + break; + case ExrConstants.AttributeNames.DataWindow: + ExrBox2i dataWindow = this.ReadBoxInteger(stream); + header.DataWindow = dataWindow; + break; + case ExrConstants.AttributeNames.DisplayWindow: + ExrBox2i displayWindow = this.ReadBoxInteger(stream); + header.DisplayWindow = displayWindow; + break; + case ExrConstants.AttributeNames.LineOrder: + var lineOrder = (ExrLineOrder)stream.ReadByte(); + header.LineOrder = lineOrder; + break; + case ExrConstants.AttributeNames.PixelAspectRatio: + float aspectRatio = stream.ReadSingle(this.buffer); + header.AspectRatio = aspectRatio; + break; + case ExrConstants.AttributeNames.ScreenWindowCenter: + float screenWindowCenterX = stream.ReadSingle(this.buffer); + float screenWindowCenterY = stream.ReadSingle(this.buffer); + header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); + break; + case ExrConstants.AttributeNames.ScreenWindowWidth: + float screenWindowWidth = stream.ReadSingle(this.buffer); + header.ScreenWindowWidth = screenWindowWidth; + break; + case ExrConstants.AttributeNames.Tiles: + header.TileXSize = this.ReadUnsignedInteger(stream); + header.TileYSize = this.ReadUnsignedInteger(stream); + break; + case ExrConstants.AttributeNames.ChunkCount: + header.ChunkCount = this.ReadSignedInteger(stream); + break; + default: + // Skip unknown attribute bytes. + stream.Skip(attribute.Length); + break; } - string attributeType = ReadString(stream); + attribute = this.ReadAttribute(stream); + } - int attributeSize = this.ReadSignedInteger(stream); + return header; + } - return new ExrAttribute(attributeName, attributeType, attributeSize); + private ExrAttribute ReadAttribute(BufferedReadStream stream) + { + string attributeName = ReadString(stream); + if (attributeName.Equals(string.Empty)) + { + return ExrAttribute.EmptyAttribute; } - private ExrBox2i ReadBoxInteger(BufferedReadStream stream) - { - int xMin = this.ReadSignedInteger(stream); - int yMin = this.ReadSignedInteger(stream); - int xMax = this.ReadSignedInteger(stream); - int yMax = this.ReadSignedInteger(stream); + string attributeType = ReadString(stream); - return new ExrBox2i(xMin, yMin, xMax, yMax); - } + int attributeSize = this.ReadSignedInteger(stream); - private List ReadChannelList(BufferedReadStream stream, int attributeSize) - { - var channels = new List(); - while (attributeSize > 1) - { - ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); - channels.Add(channelInfo); - attributeSize -= bytesRead; - } + return new ExrAttribute(attributeName, attributeType, attributeSize); + } - // Last byte should be a null byte. - int byteRead = stream.ReadByte(); + private ExrBox2i ReadBoxInteger(BufferedReadStream stream) + { + int xMin = this.ReadSignedInteger(stream); + int yMin = this.ReadSignedInteger(stream); + int xMax = this.ReadSignedInteger(stream); + int yMax = this.ReadSignedInteger(stream); - return channels; - } + return new ExrBox2i(xMin, yMin, xMax, yMax); + } - private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) + private List ReadChannelList(BufferedReadStream stream, int attributeSize) + { + var channels = new List(); + while (attributeSize > 1) { - string channelName = ReadString(stream); - bytesRead = channelName.Length + 1; + ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); + channels.Add(channelInfo); + attributeSize -= bytesRead; + } - var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); - bytesRead += 4; + // Last byte should be a null byte. + int byteRead = stream.ReadByte(); - byte pLinear = (byte)stream.ReadByte(); - byte reserved0 = (byte)stream.ReadByte(); - byte reserved1 = (byte)stream.ReadByte(); - byte reserved2 = (byte)stream.ReadByte(); - bytesRead += 4; + return channels; + } - int xSampling = this.ReadSignedInteger(stream); - bytesRead += 4; + private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) + { + string channelName = ReadString(stream); + bytesRead = channelName.Length + 1; - int ySampling = this.ReadSignedInteger(stream); - bytesRead += 4; + var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); + bytesRead += 4; - return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); - } + byte pLinear = (byte)stream.ReadByte(); + byte reserved0 = (byte)stream.ReadByte(); + byte reserved1 = (byte)stream.ReadByte(); + byte reserved2 = (byte)stream.ReadByte(); + bytesRead += 4; + + int xSampling = this.ReadSignedInteger(stream); + bytesRead += 4; + + int ySampling = this.ReadSignedInteger(stream); + bytesRead += 4; - private static string ReadString(BufferedReadStream stream) + return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); + } + + private static string ReadString(BufferedReadStream stream) + { + var str = new StringBuilder(); + int character = stream.ReadByte(); + if (character == 0) { - var str = new StringBuilder(); - int character = stream.ReadByte(); - if (character == 0) - { - // End of file header reached. - return string.Empty; - } + // End of file header reached. + return string.Empty; + } - while (character != 0) + while (character != 0) + { + if (character == -1) { - if (character == -1) - { - ExrThrowHelper.ThrowInvalidImageHeader(); - } - - str.Append((char)character); - character = stream.ReadByte(); + ExrThrowHelper.ThrowInvalidImageHeader(); } - return str.ToString(); + str.Append((char)character); + character = stream.ReadByte(); } - private ExrPixelType FindPixelType() + return str.ToString(); + } + + private ExrPixelType FindPixelType() + { + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) { - ExrPixelType? pixelType = null; - for (int i = 0; i < this.Channels.Count; i++) + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance)) { - if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance)) + if (!pixelType.HasValue) { - if (!pixelType.HasValue) - { - pixelType = this.Channels[i].PixelType; - } - else + pixelType = this.Channels[i].PixelType; + } + else + { + if (pixelType != this.Channels[i].PixelType) { - if (pixelType != this.Channels[i].PixelType) - { - ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); - } + ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); } } } + } - if (!pixelType.HasValue) - { - ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); - } - - return pixelType.Value; + if (!pixelType.HasValue) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); } - private void IsSupportedCompression() + return pixelType.Value; + } + + private void IsSupportedCompression() + { + if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) { - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) - { - ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); - } + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } + } - private void ReadImageDataType() + private void ReadImageDataType() + { + bool hasRedChannel = false; + bool hasGreenChannel = false; + bool hasBlueChannel = false; + bool hasAlphaChannel = false; + bool hasLuminance = false; + foreach (ExrChannelInfo channelInfo in this.Channels) { - bool hasRedChannel = false; - bool hasGreenChannel = false; - bool hasBlueChannel = false; - bool hasAlphaChannel = false; - bool hasLuminance = false; - foreach (ExrChannelInfo channelInfo in this.Channels) + if (channelInfo.ChannelName.Equals("A")) { - if (channelInfo.ChannelName.Equals("A")) - { - hasAlphaChannel = true; - } - - if (channelInfo.ChannelName.Equals("R")) - { - hasRedChannel = true; - } - - if (channelInfo.ChannelName.Equals("G")) - { - hasGreenChannel = true; - } - - if (channelInfo.ChannelName.Equals("B")) - { - hasBlueChannel = true; - } - - if (channelInfo.ChannelName.Equals("Y")) - { - hasLuminance = true; - } + hasAlphaChannel = true; } - if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + if (channelInfo.ChannelName.Equals("R")) { - this.ImageDataType = ExrImageDataType.Rgba; - return; + hasRedChannel = true; } - if (hasRedChannel && hasGreenChannel && hasBlueChannel) + if (channelInfo.ChannelName.Equals("G")) { - this.ImageDataType = ExrImageDataType.Rgb; - return; + hasGreenChannel = true; } - if (hasLuminance && this.Channels.Count == 1) + if (channelInfo.ChannelName.Equals("B")) { - this.ImageDataType = ExrImageDataType.Gray; - return; + hasBlueChannel = true; } - ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); - } - - private bool HasAlpha() - { - foreach (ExrChannelInfo channelInfo in this.Channels) + if (channelInfo.ChannelName.Equals("Y")) { - if (channelInfo.ChannelName.Equals("A")) - { - return true; - } + hasLuminance = true; } + } - return false; + if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + { + this.ImageDataType = ExrImageDataType.Rgba; + return; } - private uint CalculateBytesPerRow(uint width) + if (hasRedChannel && hasGreenChannel && hasBlueChannel) { - uint bytesPerRow = 0; - foreach (ExrChannelInfo channelInfo in this.Channels) - { - if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) - { - if (channelInfo.PixelType == ExrPixelType.Half) - { - bytesPerRow += 2 * width; - } - else - { - bytesPerRow += 4 * width; - } - } - } + this.ImageDataType = ExrImageDataType.Rgb; + return; + } - return bytesPerRow; + if (hasLuminance && this.Channels.Count == 1) + { + this.ImageDataType = ExrImageDataType.Gray; + return; } - private uint RowsPerBlock() + ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); + } + + private bool HasAlpha() + { + foreach (ExrChannelInfo channelInfo in this.Channels) { - switch (this.Compression) + if (channelInfo.ChannelName.Equals("A")) { - case ExrCompressionType.Zip: - case ExrCompressionType.Pxr24: - return 16; - case ExrCompressionType.B44: - case ExrCompressionType.B44A: - case ExrCompressionType.Piz: - return 32; - - default: - return 1; + return true; } } - private ulong ReadUnsignedLong(BufferedReadStream stream) + return false; + } + + private uint CalculateBytesPerRow(uint width) + { + uint bytesPerRow = 0; + foreach (ExrChannelInfo channelInfo in this.Channels) { - int bytesRead = stream.Read(this.buffer, 0, 8); - if (bytesRead != 8) + if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + if (channelInfo.PixelType == ExrPixelType.Half) + { + bytesPerRow += 2 * width; + } + else + { + bytesPerRow += 4 * width; + } } - - return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); } - private uint ReadUnsignedInteger(BufferedReadStream stream) + return bytesPerRow; + } + + private uint RowsPerBlock() + { + switch (this.Compression) { - int bytesRead = stream.Read(this.buffer, 0, 4); - if (bytesRead != 4) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); - } + case ExrCompressionType.Zip: + case ExrCompressionType.Pxr24: + return 16; + case ExrCompressionType.B44: + case ExrCompressionType.B44A: + case ExrCompressionType.Piz: + return 32; + + default: + return 1; + } + } - return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + private ulong ReadUnsignedLong(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 8); + if (bytesRead != 8) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } - private int ReadSignedInteger(BufferedReadStream stream) + return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); + } + + private uint ReadUnsignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) { - int bytesRead = stream.Read(this.buffer, 0, 4); - if (bytesRead != 4) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); - } + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + } - return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); + private int ReadSignedInteger(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } + + return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs new file mode 100644 index 000000000..f29e9cfb9 --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image decoder options for decoding OpenExr streams. +/// +public sealed class ExrDecoderOptions : ISpecializedDecoderOptions +{ + /// + public DecoderOptions GeneralOptions { get; set; } = new(); +} diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs index 71d69596a..54145afda 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs @@ -1,38 +1,34 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image encoder for writing an image to a stream in the OpenExr Format. +/// +public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions { /// - /// Image encoder for writing an image to a stream in the OpenExr Format. + /// Gets or sets the pixel type of the image. /// - public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions - { - /// - /// Gets or sets the pixel type of the image. - /// - public ExrPixelType? PixelType { get; set; } + public ExrPixelType? PixelType { get; set; } - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new ExrEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index eb626dc1b..76e186eb8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -1,422 +1,417 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading; using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Image encoder for writing an image to a stream in the OpenExr format. +/// +internal sealed class ExrEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream in the OpenExr format. + /// Reusable buffer. /// - internal sealed class ExrEncoderCore : IImageEncoderInternals - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[8]; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The pixel type of the image. - /// - private ExrPixelType? pixelType; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options. - /// The memory manager. - public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.pixelType = options.PixelType; - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + private readonly byte[] buffer = new byte[8]; - Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; - - ImageMetadata metadata = image.Metadata; - ExrMetadata exrMetadata = metadata.GetExrMetadata(); - this.pixelType ??= exrMetadata.PixelType; - int width = image.Width; - int height = image.Height; - var header = new ExrHeaderAttributes() - { - Compression = ExrCompressionType.None, - AspectRatio = 1.0f, - DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), - DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), - LineOrder = ExrLineOrder.IncreasingY, - ScreenWindowCenter = new PointF(0.0f, 0.0f), - ScreenWindowWidth = 1, - Channels = new List() - { - new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), - } - }; - - // Write magick bytes. - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - - // Version number. - this.buffer[0] = 2; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - // Second, third and fourth bytes store info about the image, set all to default: zero. - this.buffer[1] = 0; - this.buffer[2] = 0; - this.buffer[3] = 0; - stream.Write(this.buffer.AsSpan(0, 4)); + /// + /// The pixel type of the image. + /// + private ExrPixelType? pixelType; - // Write EXR header. - this.WriteHeader(stream, header); + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.pixelType = options.PixelType; + } - // Write offsets to each pixel row. - int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; - int numberOfChannels = 3; - uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); - this.WriteRowOffsets(stream, height, rowSizeBytes); + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - // Write pixel data. - switch (this.pixelType) - { - case ExrPixelType.Half: - case ExrPixelType.Float: - this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); - break; - case ExrPixelType.UnsignedInt: - this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); - break; - } - } + Buffer2D pixels = image.Frames.RootFrame.PixelBuffer; - private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) - where TPixel : unmanaged, IPixel + ImageMetadata metadata = image.Metadata; + ExrMetadata exrMetadata = metadata.GetExrMetadata(); + this.pixelType ??= exrMetadata.PixelType; + int width = image.Width; + int height = image.Height; + var header = new ExrHeaderAttributes() { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); - Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); - Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - - for (int y = 0; y < height; y++) + Compression = ExrCompressionType.None, + AspectRatio = 1.0f, + DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), + DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), + LineOrder = ExrLineOrder.IncreasingY, + ScreenWindowCenter = new PointF(0.0f, 0.0f), + ScreenWindowWidth = 1, + Channels = new List() { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - - for (int x = 0; x < width; x++) - { - var vector4 = pixelRowSpan[x].ToVector4(); - redBuffer[x] = vector4.X; - greenBuffer[x] = vector4.Y; - blueBuffer[x] = vector4.Z; - } - - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); - - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - - switch (this.pixelType) - { - case ExrPixelType.Float: - this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); - break; - case ExrPixelType.Half: - this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); - break; - } + new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), } - } - - private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); - Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); - Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - - var rgb = default(Rgb96); - for (int y = 0; y < height; y++) - { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + }; - for (int x = 0; x < width; x++) - { - var vector4 = pixelRowSpan[x].ToVector4(); - rgb.FromVector4(vector4); + // Write magick bytes. + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); + stream.Write(this.buffer.AsSpan(0, 4)); - redBuffer[x] = rgb.R; - greenBuffer[x] = rgb.G; - blueBuffer[x] = rgb.B; - } + // Version number. + this.buffer[0] = 2; - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); + // Second, third and fourth bytes store info about the image, set all to default: zero. + this.buffer[1] = 0; + this.buffer[2] = 0; + this.buffer[3] = 0; + stream.Write(this.buffer.AsSpan(0, 4)); - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write EXR header. + this.WriteHeader(stream, header); - this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); - } - } + // Write offsets to each pixel row. + int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; + int numberOfChannels = 3; + uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); + this.WriteRowOffsets(stream, height, rowSizeBytes); - private void WriteHeader(Stream stream, ExrHeaderAttributes header) + // Write pixel data. + switch (this.pixelType) { - this.WriteChannels(stream, header.Channels); - this.WriteCompression(stream, header.Compression.Value); - this.WriteDataWindow(stream, header.DataWindow.Value); - this.WriteDisplayWindow(stream, header.DisplayWindow.Value); - this.WritePixelAspectRatio(stream, header.AspectRatio.Value); - this.WriteLineOrder(stream, header.LineOrder.Value); - this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); - this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); - stream.WriteByte(0); + case ExrPixelType.Half: + case ExrPixelType.Float: + this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); + break; + case ExrPixelType.UnsignedInt: + this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); + break; } + } + + private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, blueBuffer[x]); - } + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - this.WriteSingle(stream, greenBuffer[x]); + var vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; } - for (int x = 0; x < width; x++) + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + + switch (this.pixelType) { - this.WriteSingle(stream, redBuffer[x]); + case ExrPixelType.Float: + this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + break; } } + } + + private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); + Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + var rgb = default(Rgb96); + for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, blueBuffer[x]); - } + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - this.WriteHalfSingle(stream, greenBuffer[x]); - } + var vector4 = pixelRowSpan[x].ToVector4(); + rgb.FromVector4(vector4); - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, redBuffer[x]); + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; } - } - private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) - { - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, blueBuffer[x]); - } + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, greenBuffer[x]); - } + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); - for (int x = 0; x < width; x++) - { - this.WriteUnsignedInt(stream, redBuffer[x]); - } + this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); } + } - private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + private void WriteHeader(Stream stream, ExrHeaderAttributes header) + { + this.WriteChannels(stream, header.Channels); + this.WriteCompression(stream, header.Compression.Value); + this.WriteDataWindow(stream, header.DataWindow.Value); + this.WriteDisplayWindow(stream, header.DisplayWindow.Value); + this.WritePixelAspectRatio(stream, header.AspectRatio.Value); + this.WriteLineOrder(stream, header.LineOrder.Value); + this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); + this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); + stream.WriteByte(0); + } + + private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); - ulong offset = startOfPixelData; - for (int i = 0; i < height; i++) - { - BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); - stream.Write(this.buffer); - offset += 4 + 4 + rowSizeBytes; - } + this.WriteSingle(stream, blueBuffer[x]); } - private void WriteChannels(Stream stream, IList channels) + for (int x = 0; x < width; x++) { - int attributeSize = 0; - foreach (ExrChannelInfo channelInfo in channels) - { - attributeSize += channelInfo.ChannelName.Length + 1; - attributeSize += 16; - } - - // Last zero byte. - attributeSize++; - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); - - foreach (ExrChannelInfo channelInfo in channels) - { - this.WriteChannelInfo(stream, channelInfo); - } + this.WriteSingle(stream, greenBuffer[x]); + } - // Last byte should be zero. - stream.WriteByte(0); + for (int x = 0; x < width; x++) + { + this.WriteSingle(stream, redBuffer[x]); } + } - private void WriteCompression(Stream stream, ExrCompressionType compression) + private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); - stream.WriteByte((byte)compression); + this.WriteHalfSingle(stream, blueBuffer[x]); } - private void WritePixelAspectRatio(Stream stream, float aspectRatio) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); - this.WriteSingle(stream, aspectRatio); + this.WriteHalfSingle(stream, greenBuffer[x]); } - private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); - stream.WriteByte((byte)lineOrder); + this.WriteHalfSingle(stream, redBuffer[x]); } + } - private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); - this.WriteSingle(stream, screenWindowCenter.X); - this.WriteSingle(stream, screenWindowCenter.Y); + this.WriteUnsignedInt(stream, blueBuffer[x]); } - private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); - this.WriteSingle(stream, screenWindowWidth); + this.WriteUnsignedInt(stream, greenBuffer[x]); } - private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + for (int x = 0; x < width; x++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); - this.WriteBoxInteger(stream, dataWindow); + this.WriteUnsignedInt(stream, redBuffer[x]); } + } - private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + { + ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); + ulong offset = startOfPixelData; + for (int i = 0; i < height; i++) { - this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); - this.WriteBoxInteger(stream, displayWindow); + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); + stream.Write(this.buffer); + offset += 4 + 4 + rowSizeBytes; } + } - private void WriteAttributeInformation(Stream stream, string name, string type, int size) + private void WriteChannels(Stream stream, IList channels) + { + int attributeSize = 0; + foreach (ExrChannelInfo channelInfo in channels) { - // Write attribute name. - this.WriteString(stream, name); + attributeSize += channelInfo.ChannelName.Length + 1; + attributeSize += 16; + } - // Write attribute type. - this.WriteString(stream, type); + // Last zero byte. + attributeSize++; + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); - // Write attribute size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); - stream.Write(this.buffer.AsSpan(0, 4)); + foreach (ExrChannelInfo channelInfo in channels) + { + this.WriteChannelInfo(stream, channelInfo); } - private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) - { - this.WriteString(stream, channelInfo.ChannelName); + // Last byte should be zero. + stream.WriteByte(0); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteCompression(Stream stream, ExrCompressionType compression) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); + stream.WriteByte((byte)compression); + } - stream.WriteByte(channelInfo.PLinear); + private void WritePixelAspectRatio(Stream stream, float aspectRatio) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); + this.WriteSingle(stream, aspectRatio); + } - // Next 3 bytes are reserved and will set to zero. - stream.WriteByte(0); - stream.WriteByte(0); - stream.WriteByte(0); + private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); + stream.WriteByte((byte)lineOrder); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); + this.WriteSingle(stream, screenWindowCenter.X); + this.WriteSingle(stream, screenWindowCenter.Y); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); - stream.Write(this.buffer.AsSpan(0, 4)); - } + private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); + this.WriteSingle(stream, screenWindowWidth); + } - private void WriteString(Stream stream, string str) - { - foreach (char c in str) - { - stream.WriteByte((byte)c); - } + private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, dataWindow); + } - // Write termination byte. - stream.WriteByte(0); - } + private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) + { + this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); + this.WriteBoxInteger(stream, displayWindow); + } - private void WriteBoxInteger(Stream stream, ExrBox2i box) - { - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); - stream.Write(this.buffer.AsSpan(0, 4)); + private void WriteAttributeInformation(Stream stream, string name, string type, int size) + { + // Write attribute name. + this.WriteString(stream, name); - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write attribute type. + this.WriteString(stream, type); - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write attribute size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); + stream.Write(this.buffer.AsSpan(0, 4)); + } - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); - stream.Write(this.buffer.AsSpan(0, 4)); - } + private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) + { + this.WriteString(stream, channelInfo.ChannelName); - [MethodImpl(InliningOptions.ShortMethod)] - private unsafe void WriteSingle(Stream stream, float value) - { - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); - stream.Write(this.buffer.AsSpan(0, 4)); - } + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); + stream.Write(this.buffer.AsSpan(0, 4)); - [MethodImpl(InliningOptions.ShortMethod)] - private void WriteHalfSingle(Stream stream, float value) - { - ushort valueAsShort = HalfTypeHelper.Pack(value); - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); - stream.Write(this.buffer.AsSpan(0, 2)); - } + stream.WriteByte(channelInfo.PLinear); + + // Next 3 bytes are reserved and will set to zero. + stream.WriteByte(0); + stream.WriteByte(0); + stream.WriteByte(0); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); + stream.Write(this.buffer.AsSpan(0, 4)); - [MethodImpl(InliningOptions.ShortMethod)] - private void WriteUnsignedInt(Stream stream, uint value) + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + private void WriteString(Stream stream, string str) + { + foreach (char c in str) { - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); - stream.Write(this.buffer.AsSpan(0, 4)); + stream.WriteByte((byte)c); } + + // Write termination byte. + stream.WriteByte(0); + } + + private void WriteBoxInteger(Stream stream, ExrBox2i box) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private unsafe void WriteSingle(Stream stream, float value) + { + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteHalfSingle(Stream stream, float value) + { + ushort valueAsShort = HalfTypeHelper.Pack(value); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + stream.Write(this.buffer.AsSpan(0, 2)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteUnsignedInt(Stream stream, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); + stream.Write(this.buffer.AsSpan(0, 4)); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs index 2cbce0970..d367977fc 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrFormat.cs @@ -1,38 +1,34 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats.Bmp; +namespace SixLabors.ImageSharp.Formats.OpenExr; -namespace SixLabors.ImageSharp.Formats.OpenExr +/// +/// Registers the image encoders, decoders and mime type detectors for the OpenExr format. +/// +public sealed class ExrFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. - /// - public sealed class ExrFormat : IImageFormat + private ExrFormat() { - private ExrFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static ExrFormat Instance { get; } = new(); + /// + /// Gets the current instance. + /// + public static ExrFormat Instance { get; } = new(); - /// - public string Name => "EXR"; + /// + public string Name => "EXR"; - /// - public string DefaultMimeType => "image/x-exr"; + /// + public string DefaultMimeType => "image/x-exr"; - /// - public IEnumerable MimeTypes => ExrConstants.MimeTypes; + /// + public IEnumerable MimeTypes => ExrConstants.MimeTypes; - /// - public IEnumerable FileExtensions => ExrConstants.FileExtensions; + /// + public IEnumerable FileExtensions => ExrConstants.FileExtensions; - /// - public ExrMetadata CreateDefaultFormatMetadata() => new(); - } + /// + public ExrMetadata CreateDefaultFormatMetadata() => new(); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs index 76d14311a..b3b3cdb60 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs @@ -1,78 +1,76 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.OpenExr.Compression; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal class ExrHeaderAttributes { - internal class ExrHeaderAttributes - { - public IList Channels { get; set; } + public IList Channels { get; set; } - public ExrCompressionType? Compression { get; set; } + public ExrCompressionType? Compression { get; set; } - public ExrBox2i? DataWindow { get; set; } + public ExrBox2i? DataWindow { get; set; } - public ExrBox2i? DisplayWindow { get; set; } + public ExrBox2i? DisplayWindow { get; set; } - public ExrLineOrder? LineOrder { get; set; } + public ExrLineOrder? LineOrder { get; set; } - public float? AspectRatio { get; set; } + public float? AspectRatio { get; set; } - public float? ScreenWindowWidth { get; set; } + public float? ScreenWindowWidth { get; set; } - public PointF? ScreenWindowCenter { get; set; } + public PointF? ScreenWindowCenter { get; set; } - public uint? TileXSize { get; set; } + public uint? TileXSize { get; set; } - public uint? TileYSize { get; set; } + public uint? TileYSize { get; set; } - public int? ChunkCount { get; set; } + public int? ChunkCount { get; set; } - public bool IsValid() + public bool IsValid() + { + if (!this.Compression.HasValue) { - if (!this.Compression.HasValue) - { - return false; - } - - if (!this.DataWindow.HasValue) - { - return false; - } - - if (!this.DisplayWindow.HasValue) - { - return false; - } - - if (!this.LineOrder.HasValue) - { - return false; - } - - if (!this.AspectRatio.HasValue) - { - return false; - } - - if (!this.ScreenWindowWidth.HasValue) - { - return false; - } - - if (!this.ScreenWindowCenter.HasValue) - { - return false; - } - - if (this.Channels is null) - { - return false; - } - - return true; + return false; } + + if (!this.DataWindow.HasValue) + { + return false; + } + + if (!this.DisplayWindow.HasValue) + { + return false; + } + + if (!this.LineOrder.HasValue) + { + return false; + } + + if (!this.AspectRatio.HasValue) + { + return false; + } + + if (!this.ScreenWindowWidth.HasValue) + { + return false; + } + + if (!this.ScreenWindowCenter.HasValue) + { + return false; + } + + if (this.Channels is null) + { + return false; + } + + return true; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs index d17c3ad61..4c3e19af8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrImageDataType { - internal enum ExrImageDataType - { - Unknown = 0, + Unknown = 0, - Rgb = 1, + Rgb = 1, - Rgba = 2, + Rgba = 2, - Gray = 3, - } + Gray = 3, } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs index 27230306d..c14ab947a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs @@ -1,31 +1,29 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Detects OpenExr file headers. +/// +public sealed class ExrImageFormatDetector : IImageFormatDetector { - /// - /// Detects OpenExr file headers. - /// - public sealed class ExrImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 4; + /// + public int HeaderSize => 4; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) - { - int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); - return fileTypeMarker == ExrConstants.MagickBytes; - } - - return false; + int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); + return fileTypeMarker == ExrConstants.MagickBytes; } + + return false; } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs index e18ee363f..a59aadbd6 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageType.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrImageType { - internal enum ExrImageType - { - ScanLine = 0, + ScanLine = 0, - Tiled = 1 - } + Tiled = 1 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs index 1005414f2..dcf1c37c9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +internal enum ExrLineOrder : byte { - internal enum ExrLineOrder : byte - { - IncreasingY = 0, + IncreasingY = 0, - DecreasingY = 1, + DecreasingY = 1, - RandomY = 2 - } + RandomY = 2 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index 7af01a38f..8b090d684 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Provides OpenExr specific metadata information for the image. +/// +public class ExrMetadata : IDeepCloneable { /// - /// Provides OpenExr specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class ExrMetadata : IDeepCloneable + public ExrMetadata() { - /// - /// Initializes a new instance of the class. - /// - public ExrMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; - /// - /// Gets or sets the pixel format. - /// - public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; + /// + /// Gets or sets the pixel format. + /// + public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; - /// - public IDeepCloneable DeepClone() => new ExrMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new ExrMetadata(this); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs index 2a57afe98..0dd49eaf8 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// The different pixel formats for a OpenEXR image. +/// +public enum ExrPixelType { /// - /// The different pixel formats for a OpenEXR image. + /// unsigned int (32 bit). /// - public enum ExrPixelType - { - /// - /// unsigned int (32 bit). - /// - UnsignedInt = 0, + UnsignedInt = 0, - /// - /// half (16 bit floating point). - /// - Half = 1, + /// + /// half (16 bit floating point). + /// + Half = 1, - /// - /// float (32 bit floating point). - /// - Float = 2 - } + /// + /// float (32 bit floating point). + /// + Float = 2 } diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs index b68fb5a2f..62f9f1b2f 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -1,32 +1,30 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Cold path optimizations for throwing exr format based exceptions. +/// +internal static class ExrThrowHelper { - /// - /// Cold path optimizations for throwing exr format based exceptions. - /// - internal static class ExrThrowHelper - { - [MethodImpl(InliningOptions.ColdPath)] - public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); } diff --git a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs deleted file mode 100644 index e61ce8537..000000000 --- a/src/ImageSharp/Formats/OpenExr/IExrDecoderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.OpenExr -{ - /// - /// Image decoder options for decoding OpenExr streams. - /// - internal interface IExrDecoderOptions - { - } -} diff --git a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs index 938236b69..ba2db62a0 100644 --- a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs +++ b/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr +namespace SixLabors.ImageSharp.Formats.OpenExr; + +/// +/// Configuration options for use during OpenExr encoding. +/// +internal interface IExrEncoderOptions { /// - /// Configuration options for use during OpenExr encoding. + /// Gets the pixel type of the image. /// - internal interface IExrEncoderOptions - { - /// - /// Gets the pixel type of the image. - /// - ExrPixelType? PixelType { get; } - } + ExrPixelType? PixelType { get; } } diff --git a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs index 24508f06a..2af0a57b0 100644 --- a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the open exr format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the open exr format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); } diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 7e233c965..b6d7f6b3f 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Buffers.Binary; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.IO; @@ -153,6 +154,31 @@ internal sealed class BufferedReadStream : Stream } } + /// + /// Reads a float value. + /// + /// The value. + public float ReadSingle(byte[] data) + { + int offset = 0; + int bytesToRead = 4; + while (bytesToRead > 0) + { + int bytesRead = this.Read(data, offset, bytesToRead); + if (bytesRead == 0) + { + throw new ImageFormatException("Not enough data to read a float value from the stream"); + } + + bytesToRead -= bytesRead; + offset += bytesRead; + } + + int intValue = BinaryPrimitives.ReadInt32BigEndian(data); + + return Unsafe.As(ref intValue); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int Read(byte[] buffer, int offset, int count) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index 9ff506efa..323000bc5 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -1,156 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Exr +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +public class ImageExtensionsTest { - [Trait("Format", "Exr")] - public class ImageExtensionsTest + [Fact] + public void SaveAsExr_Stream() { - [Fact] - public void SaveAsExr_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsExr_Path.exr"); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(file); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } + using var memoryStream = new MemoryStream(); - [Fact] - public async Task SaveAsExrAsync_Path() + using (var image = new Image(10, 10)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(file); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + image.SaveAsOpenExr(memoryStream, new ExrEncoder()); } - [Fact] - public void SaveAsExr_Path_Encoder() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(file, new ExrEncoder()); - } + memoryStream.Position = 0; - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } - - [Fact] - public async Task SaveAsExrAsync_Path_Encoder() + using (Image.Load(memoryStream, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff"); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(file, new ExrEncoder()); - } - - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + Assert.Equal("image/x-exr", mime.DefaultMimeType); } + } - [Fact] - public void SaveAsExr_Stream() - { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(memoryStream); - } - - memoryStream.Position = 0; - - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } - } + [Fact] + public void SaveAsExr_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - [Fact] - public async Task SaveAsExrAsync_StreamAsync() + using (var image = new Image(10, 10)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(memoryStream); - } - - memoryStream.Position = 0; - - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + image.SaveAsOpenExr(memoryStream, new ExrEncoder()); } - [Fact] - public void SaveAsExr_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsOpenExr(memoryStream, new ExrEncoder()); - } - - memoryStream.Position = 0; + memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); } + } - [Fact] - public async Task SaveAsExrAsync_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + [Fact] + public async Task SaveAsExrAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (var image = new Image(10, 10)) - { - await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); + } - memoryStream.Position = 0; + memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-exr", mime.DefaultMimeType); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-exr", mime.DefaultMimeType); } } } + diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 0fa7f1684..fa5e4bdef 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -144,11 +144,6 @@ public class GeneralFormatTests { image.SaveAsTiff(output); } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.exr"))) - { - image.SaveAsOpenExr(output); - } } } From ce21f1afaa22e53e8d5686f483f854f713a263b3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 20 Sep 2022 17:25:04 +0200 Subject: [PATCH 016/240] Add exr decoder tests --- src/ImageSharp/Configuration.cs | 4 +- .../Formats/Exr/ExrDecoderTests.cs | 54 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 8 +++ .../TestUtilities/TestEnvironment.Formats.cs | 6 ++- tests/Images/Input/Exr/Calliphora_rle.exr | 3 ++ .../Input/Exr/Calliphora_uncompressed.exr | 3 ++ tests/Images/Input/Exr/Calliphora_zip.exr | 3 ++ tests/Images/Input/Exr/Calliphora_zips.exr | 3 ++ 8 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs create mode 100644 tests/Images/Input/Exr/Calliphora_rle.exr create mode 100644 tests/Images/Input/Exr/Calliphora_uncompressed.exr create mode 100644 tests/Images/Input/Exr/Calliphora_zip.exr create mode 100644 tests/Images/Input/Exr/Calliphora_zips.exr diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 474e91a43..f00cc0eaa 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -213,6 +213,7 @@ public sealed class Configuration /// . /// . /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() => new( @@ -223,5 +224,6 @@ public sealed class Configuration new PbmConfigurationModule(), new TgaConfigurationModule(), new TiffConfigurationModule(), - new WebpConfigurationModule()); + new WebpConfigurationModule(), + new ExrConfigurationModule()); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs new file mode 100644 index 000000000..709a44263 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrDecoderTests +{ + private static ExrDecoder ExrDecoder => new(); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + [Theory] + [WithFile(TestImages.Exr.Zip, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_ZipCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + [Theory] + [WithFile(TestImages.Exr.Zips, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_ZipsCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + [Theory] + [WithFile(TestImages.Exr.Rle, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_RunLengthCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 676d460e5..8922e3ebe 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -998,4 +998,12 @@ public static class TestImages public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; } + + public static class Exr + { + public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; + public const string Zip = "Exr/Calliphora_zip.exr"; + public const string Zips = "Exr/Calliphora_zips.exr"; + public const string Rle = "Exr/Calliphora_rle.exr"; + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 89b43a066..758d68902 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -59,12 +60,13 @@ public static partial class TestEnvironment new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(), - new TiffConfigurationModule()); + new TiffConfigurationModule(), + new ExrConfigurationModule()); IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); - // Magick codecs should work on all platforms + // Magick codecs should work on all platforms. cfg.ConfigureCodecs( PngFormat.Instance, MagickReferenceDecoder.Instance, diff --git a/tests/Images/Input/Exr/Calliphora_rle.exr b/tests/Images/Input/Exr/Calliphora_rle.exr new file mode 100644 index 000000000..20878f889 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_rle.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6302e72676f5574b8a9dde7e4385cff1c115e9833b630118328b88aea07e31d0 +size 4098454 diff --git a/tests/Images/Input/Exr/Calliphora_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_uncompressed.exr new file mode 100644 index 000000000..f0003a1f9 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71613ff5972fa9e7b405ac944d159d1791ad229f5560da616438c9d718eafd24 +size 5798633 diff --git a/tests/Images/Input/Exr/Calliphora_zip.exr b/tests/Images/Input/Exr/Calliphora_zip.exr new file mode 100644 index 000000000..5d17150a4 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_zip.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92c78a7cf9fce29e725511acadf49f0f784914da5bef55b0e0c4e75ba09c3a75 +size 2652397 diff --git a/tests/Images/Input/Exr/Calliphora_zips.exr b/tests/Images/Input/Exr/Calliphora_zips.exr new file mode 100644 index 000000000..b4b297682 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_zips.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e34a9b0bb7fd6eb24eef633dea737653812fd8ab5c5352f6e10523055823b1d +size 2918571 From a9620ee52fb319ac6c439038555460d87485b4f3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Sep 2022 19:35:37 +0200 Subject: [PATCH 017/240] Add support for decoding b44 compressed exr files --- .../Decompressors/B44Compression.cs | 191 ++++++++++++++++++ .../NoneExrCompression.cs | 2 +- .../RunLengthCompression.cs | 16 +- .../ZipExrCompression.cs | 8 +- .../Compression/ExrDecompressorFactory.cs | 6 +- .../Formats/OpenExr/ExrDecoderCore.cs | 39 +++- .../Formats/OpenExr/ExrEncoderCore.cs | 13 +- .../Formats/OpenExr/ExrThrowHelper.cs | 8 - .../Formats/Exr/ExrDecoderTests.cs | 10 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/Calliphora_b44.exr | 3 + 11 files changed, 256 insertions(+), 41 deletions(-) create mode 100644 src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs rename src/ImageSharp/Formats/OpenExr/Compression/{Compressors => Decompressors}/NoneExrCompression.cs (89%) rename src/ImageSharp/Formats/OpenExr/Compression/{Compressors => Decompressors}/RunLengthCompression.cs (82%) rename src/ImageSharp/Formats/OpenExr/Compression/{Compressors => Decompressors}/ZipExrCompression.cs (86%) create mode 100644 tests/Images/Input/Exr/Calliphora_b44.exr diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs new file mode 100644 index 000000000..55e9003bc --- /dev/null +++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs @@ -0,0 +1,191 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; + +internal class B44Compression : ExrBaseDecompressor +{ + private readonly int width; + + private readonly int height; + + private readonly uint rowsPerBlock; + + private readonly int channelCount; + + private byte[] scratch = new byte[14]; + + private ushort[] s = new ushort[16]; + + private IMemoryOwner tmpBuffer; + + public B44Compression(MemoryAllocator allocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) + : base(allocator, uncompressedBytes) + { + this.width = width; + this.height = height; + this.rowsPerBlock = rowsPerBlock; + this.channelCount = channelCount; + this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); + } + + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span outputBuffer = MemoryMarshal.Cast(buffer); + Span decompressed = this.tmpBuffer.GetSpan(); + int outputOffset = 0; + int bytesLeft = (int)compressedBytes; + for (int i = 0; i < this.channelCount && bytesLeft > 0; i++) + { + for (int y = 0; y < this.rowsPerBlock; y += 4) + { + Span row0 = decompressed.Slice(outputOffset, this.width); + outputOffset += this.width; + Span row1 = decompressed.Slice(outputOffset, this.width); + outputOffset += this.width; + Span row2 = decompressed.Slice(outputOffset, this.width); + outputOffset += this.width; + Span row3 = decompressed.Slice(outputOffset, this.width); + outputOffset += this.width; + + int rowOffset = 0; + for (int x = 0; x < this.width && bytesLeft > 0; x += 4) + { + int bytesRead = stream.Read(this.scratch, 0, 3); + if (bytesRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); + } + + if (this.scratch[2] >= 13 << 2) + { + Unpack3(this.scratch, this.s); + bytesLeft -= 3; + } + else + { + bytesRead = stream.Read(this.scratch, 3, 11); + if (bytesRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); + } + + Unpack14(this.scratch, this.s); + bytesLeft -= 14; + } + + int n = x + 3 < this.width ? 4 : this.width - x; + if (y + 3 < this.rowsPerBlock) + { + this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); + this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); + this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); + this.s.AsSpan(12, n).CopyTo(row3.Slice(rowOffset)); + } + else + { + this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); + if (y + 1 < this.rowsPerBlock) + { + this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); + } + + if (y + 2 < this.rowsPerBlock) + { + this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); + } + } + + rowOffset += 4; + } + + if (bytesLeft <= 0) + { + break; + } + } + } + + // Rearrange the decompressed data such that the data for each scan line form a contiguous block. + int offsetDecompressed = 0; + int offsetOutput = 0; + int blockSize = (int)(this.width * this.rowsPerBlock); + for (int y = 0; y < this.rowsPerBlock; y++) + { + for (int i = 0; i < this.channelCount; i++) + { + decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer.Slice(offsetOutput)); + offsetOutput += this.width; + } + + offsetDecompressed += this.width; + } + } + + // Unpack a 14-byte block into 4 by 4 16-bit pixels. + private static void Unpack14(Span b, Span s) + { + s[0] = (ushort)((b[0] << 8) | b[1]); + + ushort shift = (ushort)(b[2] >> 2); + ushort bias = (ushort)(0x20u << shift); + + s[4] = (ushort)(s[0] + ((((b[2] << 4) | (b[3] >> 4)) & 0x3fu) << shift) - bias); + s[8] = (ushort)(s[4] + ((((b[3] << 2) | (b[4] >> 6)) & 0x3fu) << shift) - bias); + s[12] = (ushort)(s[8] + ((b[4] & 0x3fu) << shift) - bias); + + s[1] = (ushort)(s[0] + ((uint)(b[5] >> 2) << shift) - bias); + s[5] = (ushort)(s[4] + ((((b[5] << 4) | (b[6] >> 4)) & 0x3fu) << shift) - bias); + s[9] = (ushort)(s[8] + ((((b[6] << 2) | (b[7] >> 6)) & 0x3fu) << shift) - bias); + s[13] = (ushort)(s[12] + ((b[7] & 0x3fu) << shift) - bias); + + s[2] = (ushort)(s[1] + ((uint)(b[8] >> 2) << shift) - bias); + s[6] = (ushort)(s[5] + ((((b[8] << 4) | (b[9] >> 4)) & 0x3fu) << shift) - bias); + s[10] = (ushort)(s[9] + ((((b[9] << 2) | (b[10] >> 6)) & 0x3fu) << shift) - bias); + s[14] = (ushort)(s[13] + ((b[10] & 0x3fu) << shift) - bias); + + s[3] = (ushort)(s[2] + ((uint)(b[11] >> 2) << shift) - bias); + s[7] = (ushort)(s[6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3fu) << shift) - bias); + s[11] = (ushort)(s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3fu) << shift) - bias); + s[15] = (ushort)(s[14] + ((b[13] & 0x3fu) << shift) - bias); + + for (int i = 0; i < 16; ++i) + { + if ((s[i] & 0x8000) != 0) + { + s[i] &= 0x7fff; + } + else + { + s[i] = (ushort)~s[i]; + } + } + } + + // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels. + private static void Unpack3(Span b, Span s) + { + s[0] = (ushort)((b[0] << 8) | b[1]); + + if ((s[0] & 0x8000) != 0) + { + s[0] &= 0x7fff; + } + else + { + s[0] = (ushort)~s[0]; + } + + for (int i = 1; i < 16; ++i) + { + s[i] = s[0]; + } + } + + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); +} diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs similarity index 89% rename from src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs index 333c453eb..f5f16a0fd 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; internal class NoneExrCompression : ExrBaseDecompressor { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs similarity index 82% rename from src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs index 08126c4de..0838dc1dc 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs @@ -5,12 +5,14 @@ using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; internal class RunLengthCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; + private readonly ushort[] s = new ushort[16]; + public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); @@ -34,12 +36,6 @@ internal class RunLengthCompression : ExrBaseDecompressor return; } - // Check the input buffer is big enough to contain 'count' bytes of remaining data. - if (compressedBytes < 0) - { - return; - } - for (int i = 0; i < count; i++) { uncompressed[offset + i] = ReadNextByte(stream); @@ -58,12 +54,6 @@ internal class RunLengthCompression : ExrBaseDecompressor return; } - // Check the input buffer is big enough to contain byte to be duplicated. - if (compressedBytes < 0) - { - return; - } - for (int i = 0; i < count + 1; i++) { uncompressed[offset + i] = value; diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs similarity index 86% rename from src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs rename to src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs index d90f684e6..e315af8e8 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Compressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; +namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; internal class ZipExrCompression : ExrBaseDecompressor { @@ -21,15 +21,15 @@ internal class ZipExrCompression : ExrBaseDecompressor Span uncompressed = this.tmpBuffer.GetSpan(); long pos = stream.Position; - using ZlibInflateStream deframeStream = new( + using ZlibInflateStream inflateStream = new( stream, () => { int left = (int)(compressedBytes - (stream.Position - pos)); return left > 0 ? left : 0; }); - deframeStream.AllocateNewBytes((int)this.UncompressedBytes, true); - DeflateStream dataStream = deframeStream.CompressedStream; + inflateStream.AllocateNewBytes((int)this.UncompressedBytes, true); + DeflateStream dataStream = inflateStream.CompressedStream; int totalRead = 0; while (totalRead < buffer.Length) diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs index 2d01efd87..538231548 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs @@ -1,14 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.OpenExr.Compression.Compressors; +using SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes) + public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) { switch (method) { @@ -20,6 +20,8 @@ internal static class ExrDecompressorFactory return new ZipExrCompression(memoryAllocator, uncompressedBytes); case ExrCompressionType.RunLengthEncoded: return new RunLengthCompression(memoryAllocator, uncompressedBytes); + case ExrCompressionType.B44: + return new B44Compression(memoryAllocator, uncompressedBytes, width, height, rowsPerBlock, channelCount); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index ca244c567..2304f3f20 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -57,12 +57,24 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals /// public Size Dimensions => new(this.Width, this.Height); + /// + /// Gets or sets the image width. + /// private int Width { get; set; } + /// + /// Gets or sets the image height. + /// private int Height { get; set; } + /// + /// Gets or sets the image channel info's. + /// private IList Channels { get; set; } + /// + /// Gets or sets the compression method. + /// private ExrCompressionType Compression { get; set; } private ExrImageDataType ImageDataType { get; set; } @@ -76,11 +88,15 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals where TPixel : unmanaged, IPixel { this.ReadExrHeader(stream); - this.IsSupportedCompression(); + if (!this.IsSupportedCompression()) + { + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + } + ExrPixelType pixelType = this.ValidateChannels(); this.ReadImageDataType(); - Image image = new Image(this.configuration, this.Width, this.Height, this.metadata); + Image image = new (this.configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); switch (pixelType) @@ -109,6 +125,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals uint bytesPerBlock = bytesPerRow * rowsPerBlock; int width = this.Width; int height = this.Height; + int channelCount = this.Channels.Count; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); @@ -118,7 +135,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); TPixel color = default; for (uint y = 0; y < height; y += rowsPerBlock) @@ -164,6 +181,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals uint bytesPerBlock = bytesPerRow * rowsPerBlock; int width = this.Width; int height = this.Height; + int channelCount = this.Channels.Count; using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); @@ -173,7 +191,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); TPixel color = default; for (uint y = 0; y < height; y += rowsPerBlock) @@ -609,12 +627,19 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals return pixelType.Value; } - private void IsSupportedCompression() + private bool IsSupportedCompression() { - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) + switch (this.Compression) { - ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + case ExrCompressionType.None: + case ExrCompressionType.Zip: + case ExrCompressionType.Zips: + case ExrCompressionType.RunLengthEncoded: + case ExrCompressionType.B44: + return true; } + + return false; } private void ReadImageDataType() diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 76e186eb8..21fb0ade4 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Memory; @@ -162,14 +163,14 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - var rgb = default(Rgb96); + Rgb96 rgb = default; for (int y = 0; y < height; y++) { Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - var vector4 = pixelRowSpan[x].ToVector4(); + Vector4 vector4 = pixelRowSpan[x].ToVector4(); rgb.FromVector4(vector4); redBuffer[x] = rgb.R; @@ -336,10 +337,10 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals private void WriteAttributeInformation(Stream stream, string name, string type, int size) { // Write attribute name. - this.WriteString(stream, name); + WriteString(stream, name); // Write attribute type. - this.WriteString(stream, type); + WriteString(stream, type); // Write attribute size. BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); @@ -348,7 +349,7 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) { - this.WriteString(stream, channelInfo.ChannelName); + WriteString(stream, channelInfo.ChannelName); BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); stream.Write(this.buffer.AsSpan(0, 4)); @@ -367,7 +368,7 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals stream.Write(this.buffer.AsSpan(0, 4)); } - private void WriteString(Stream stream, string str) + private static void WriteString(Stream stream, string str) { foreach (char c in str) { diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs index 62f9f1b2f..b609d0038 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; - namespace SixLabors.ImageSharp.Formats.OpenExr; /// @@ -10,21 +8,15 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; /// internal static class ExrThrowHelper { - [MethodImpl(InliningOptions.ColdPath)] public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); - [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); - [MethodImpl(InliningOptions.ColdPath)] public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); - [MethodImpl(InliningOptions.ColdPath)] public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); - [MethodImpl(InliningOptions.ColdPath)] public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 709a44263..29b75bc9c 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -51,4 +51,14 @@ public class ExrDecoderTests image.DebugSave(provider); image.CompareToOriginal(provider); } + + [Theory] + [WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_B44Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8922e3ebe..25187b0fc 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1005,5 +1005,6 @@ public static class TestImages public const string Zip = "Exr/Calliphora_zip.exr"; public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; + public const string B44 = "Exr/Calliphora_b44.exr"; } } diff --git a/tests/Images/Input/Exr/Calliphora_b44.exr b/tests/Images/Input/Exr/Calliphora_b44.exr new file mode 100644 index 000000000..ebe464170 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_b44.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cde761051241cba1e7f8466d71602abdb130677de55602fe6afec4b287c75b9d +size 2533521 From ec8163a3fabe4880347286b6fb7edd7c7c972335 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 23 Sep 2022 19:49:00 +0200 Subject: [PATCH 018/240] Fix warnings --- .../Formats/OpenExr/ExrAttribute.cs | 3 +- .../Formats/OpenExr/ExrDecoderCore.cs | 87 ++--- .../PixelImplementations/Rgb96.cs | 324 +++++++++-------- .../PixelImplementations/Rgba128.cs | 340 +++++++++--------- .../Formats/Exr/ImageExtensionsTest.cs | 3 +- 5 files changed, 377 insertions(+), 380 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs index ad5a8290d..0d6ca86cc 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Six Labors Split License.. +// Licensed under the Six Labors Split License. using System.Diagnostics; @@ -23,4 +23,3 @@ internal class ExrAttribute public int Length { get; } } - diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 2304f3f20..3e554fb74 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -96,7 +96,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals ExrPixelType pixelType = this.ValidateChannels(); this.ReadImageDataType(); - Image image = new (this.configuration, this.Width, this.Height, this.metadata); + Image image = new(this.configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); switch (pixelType) @@ -156,16 +156,15 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + offset += ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } for (int x = 0; x < width; x++) { - var pixelValue = new HalfVector4(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); + HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); color.FromVector4(pixelValue.ToVector4()); pixelRow[x] = color; } - } stream.Position = nextRowOffsetPosition; @@ -219,7 +218,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals for (int x = 0; x < width; x++) { - var pixelValue = new Rgba128(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); + Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); color.FromVector4(pixelValue.ToVector4()); pixelRow[x] = color; } @@ -227,7 +226,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals } } - private int ReadFloatChannelData( + private static int ReadFloatChannelData( BufferedReadStream stream, ExrChannelInfo channel, Span decompressedPixelData, @@ -240,19 +239,19 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals switch (channel.ChannelName) { case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + return ReadChannelData(channel, decompressedPixelData, redPixelData, width); case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + return ReadChannelData(channel, decompressedPixelData, bluePixelData, width); case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + return ReadChannelData(channel, decompressedPixelData, greenPixelData, width); case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + return ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + int bytesRead = ReadChannelData(channel, decompressedPixelData, redPixelData, width); redPixelData.CopyTo(bluePixelData); redPixelData.CopyTo(greenPixelData); @@ -279,19 +278,19 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals switch (channel.ChannelName) { case ExrConstants.ChannelNames.Red: - return this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + return ReadChannelData(channel, decompressedPixelData, redPixelData, width); case ExrConstants.ChannelNames.Blue: - return this.ReadChannelData(channel, decompressedPixelData, bluePixelData, width); + return ReadChannelData(channel, decompressedPixelData, bluePixelData, width); case ExrConstants.ChannelNames.Green: - return this.ReadChannelData(channel, decompressedPixelData, greenPixelData, width); + return ReadChannelData(channel, decompressedPixelData, greenPixelData, width); case ExrConstants.ChannelNames.Alpha: - return this.ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); + return ReadChannelData(channel, decompressedPixelData, alphaPixelData, width); case ExrConstants.ChannelNames.Luminance: - int bytesRead = this.ReadChannelData(channel, decompressedPixelData, redPixelData, width); + int bytesRead = ReadChannelData(channel, decompressedPixelData, redPixelData, width); redPixelData.CopyTo(bluePixelData); redPixelData.CopyTo(greenPixelData); return bytesRead; @@ -304,31 +303,31 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals } } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) { switch (channel.PixelType) { case ExrPixelType.Half: - return this.ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); + return ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); case ExrPixelType.Float: - return this.ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); + return ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); } return 0; } - private int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) { switch (channel.PixelType) { case ExrPixelType.UnsignedInt: - return this.ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); + return ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); } return 0; } - private int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) + private static int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; for (int x = 0; x < width; x++) @@ -341,7 +340,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals return offset; } - private int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) + private static int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; for (int x = 0; x < width; x++) @@ -354,7 +353,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals return offset; } - private int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) + private static int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) { int offset = 0; for (int x = 0; x < width; x++) @@ -449,7 +448,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) { ExrAttribute attribute = this.ReadAttribute(stream); - var header = new ExrHeaderAttributes(); + ExrHeaderAttributes header = new(); while (!attribute.Equals(ExrAttribute.EmptyAttribute)) { @@ -471,7 +470,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals header.DisplayWindow = displayWindow; break; case ExrConstants.AttributeNames.LineOrder: - var lineOrder = (ExrLineOrder)stream.ReadByte(); + ExrLineOrder lineOrder = (ExrLineOrder)stream.ReadByte(); header.LineOrder = lineOrder; break; case ExrConstants.AttributeNames.PixelAspectRatio: @@ -509,7 +508,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private ExrAttribute ReadAttribute(BufferedReadStream stream) { string attributeName = ReadString(stream); - if (attributeName.Equals(string.Empty)) + if (attributeName.Equals(string.Empty, StringComparison.Ordinal)) { return ExrAttribute.EmptyAttribute; } @@ -533,7 +532,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private List ReadChannelList(BufferedReadStream stream, int attributeSize) { - var channels = new List(); + List channels = new(); while (attributeSize > 1) { ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); @@ -552,7 +551,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals string channelName = ReadString(stream); bytesRead = channelName.Length + 1; - var pixelType = (ExrPixelType)this.ReadSignedInteger(stream); + ExrPixelType pixelType = (ExrPixelType)this.ReadSignedInteger(stream); bytesRead += 4; byte pLinear = (byte)stream.ReadByte(); @@ -572,7 +571,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private static string ReadString(BufferedReadStream stream) { - var str = new StringBuilder(); + StringBuilder str = new(); int character = stream.ReadByte(); if (character == 0) { @@ -599,11 +598,11 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals ExrPixelType? pixelType = null; for (int i = 0; i < this.Channels.Count; i++) { - if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance)) + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance, StringComparison.Ordinal)) { if (!pixelType.HasValue) { @@ -651,27 +650,27 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals bool hasLuminance = false; foreach (ExrChannelInfo channelInfo in this.Channels) { - if (channelInfo.ChannelName.Equals("A")) + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) { hasAlphaChannel = true; } - if (channelInfo.ChannelName.Equals("R")) + if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)) { hasRedChannel = true; } - if (channelInfo.ChannelName.Equals("G")) + if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)) { hasGreenChannel = true; } - if (channelInfo.ChannelName.Equals("B")) + if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)) { hasBlueChannel = true; } - if (channelInfo.ChannelName.Equals("Y")) + if (channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) { hasLuminance = true; } @@ -702,7 +701,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals { foreach (ExrChannelInfo channelInfo in this.Channels) { - if (channelInfo.ChannelName.Equals("A")) + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) { return true; } @@ -716,7 +715,11 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals uint bytesPerRow = 0; foreach (ExrChannelInfo channelInfo in this.Channels) { - if (channelInfo.ChannelName.Equals("A") || channelInfo.ChannelName.Equals("R") || channelInfo.ChannelName.Equals("G") || channelInfo.ChannelName.Equals("B") || channelInfo.ChannelName.Equals("Y")) + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) { if (channelInfo.PixelType == ExrPixelType.Half) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 2809a4c31..620e305a2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -1,175 +1,173 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgb96 : IPixel { + private const float InvMax = 1.0f / uint.MaxValue; + + private const double Max = uint.MaxValue; + + /// + /// Gets the red component. + /// + public uint R; + + /// + /// Gets the green component. + /// + public uint G; + + /// + /// Gets the blue component. + /// + public uint B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb96(uint r, uint g, uint b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); + /// - /// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Compares two objects for equality. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Rgb96 : IPixel + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) { - private const float InvMax = 1.0f / uint.MaxValue; - - private const double Max = uint.MaxValue; - - /// - /// Gets the red component. - /// - public uint R; - - /// - /// Gets the green component. - /// - public uint G; - - /// - /// Gets the blue component. - /// - public uint B; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb96(uint r, uint g, uint b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.R = (uint)(vector.X * Max); - this.G = (uint)(vector.Y * Max); - this.B = (uint)(vector.Z * Max); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R * InvMax, - this.G * InvMax, - this.B * InvMax, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.R = (uint)(vector.X * Max); + this.G = (uint)(vector.Y * Max); + this.B = (uint)(vector.Z * Max); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new( + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index 01fc16d45..912a62db0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue and alpha. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba128 : IPixel { + private const float InvMax = 1.0f / uint.MaxValue; + + private const double Max = uint.MaxValue; + + /// + /// Gets the red component. + /// + public uint R; + + /// + /// Gets the green component. + /// + public uint G; + + /// + /// Gets the blue component. + /// + public uint B; + /// - /// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue and alpha. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets the alpha channel. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Rgba128 : IPixel + public uint A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba128(uint r, uint g, uint b, uint a) { - private const float InvMax = 1.0f / uint.MaxValue; - - private const double Max = uint.MaxValue; - - /// - /// Gets the red component. - /// - public uint R; - - /// - /// Gets the green component. - /// - public uint G; - - /// - /// Gets the blue component. - /// - public uint B; - - /// - /// Gets the alpha channel. - /// - public uint A; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba128(uint r, uint g, uint b, uint a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.R = (uint)(vector.X * Max); - this.G = (uint)(vector.Y * Max); - this.B = (uint)(vector.Z * Max); - this.A = (uint)(vector.W * Max); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R * InvMax, - this.G * InvMax, - this.B * InvMax, - this.A * InvMax); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); - - /// - public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + this.R = r; + this.G = g; + this.B = b; + this.A = a; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.R = (uint)(vector.X * Max); + this.G = (uint)(vector.Y * Max); + this.B = (uint)(vector.Z * Max); + this.A = (uint)(vector.W * Max); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new( + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, + this.A * InvMax); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + + /// + public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index 323000bc5..926f5ca90 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.OpenExr; @@ -64,4 +64,3 @@ public class ImageExtensionsTest } } } - From 28dcdf2f0c2912891d8e5b2346ed2c93a5f1d5d2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 7 Apr 2023 21:10:05 +0200 Subject: [PATCH 019/240] Add DecodeTiledFloatingPointPixelData --- .../Formats/OpenExr/ExrDecoderCore.cs | 126 +++++++++++++++++- 1 file changed, 121 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index d414babf2..2baa60b49 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -81,7 +81,11 @@ namespace SixLabors.ImageSharp.Formats.OpenExr where TPixel : unmanaged, IPixel { this.ReadExrHeader(stream); - this.IsSupportedCompression(); + if (!this.IsSupportedCompression()) + { + ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + } + ExrPixelType pixelType = this.ValidateChannels(); this.ReadImageDataType(); @@ -92,7 +96,15 @@ namespace SixLabors.ImageSharp.Formats.OpenExr { case ExrPixelType.Half: case ExrPixelType.Float: - this.DecodeFloatingPointPixelData(stream, pixels); + if (this.ImageType is ExrImageType.ScanLine) + { + this.DecodeFloatingPointPixelData(stream, pixels); + } + else + { + this.DecodeTiledFloatingPointPixelData(stream, pixels); + } + break; case ExrPixelType.UnsignedInt: this.DecodeUnsignedIntPixelData(stream, pixels); @@ -214,6 +226,97 @@ namespace SixLabors.ImageSharp.Formats.OpenExr } } + private void DecodeTiledFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + if (!this.HeaderAttributes.ChunkCount.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Missing chunk count in tiled image"); + } + + int chunks = this.HeaderAttributes.ChunkCount.Value; + + bool hasAlpha = this.HasAlpha(); + uint tileWidth = (uint)this.HeaderAttributes.TileXSize; + uint tileHeight = (uint)this.HeaderAttributes.TileYSize; + int width = this.Width; + int height = this.Height; + uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); + uint bytesPerBlockRow = this.CalculateBytesPerRow(tileWidth); + uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerBlock = bytesPerBlockRow * rowsPerBlock; + uint columnsPerBlock = (uint)(this.Height / tileHeight); + uint tilesPerScanline = (uint)((this.Width + tileWidth - 1) / tileWidth); + + using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate((int)(tileWidth * 4)); + using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((width * height) / chunks); + Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); + Span redPixelData = rowBuffer.GetSpan().Slice(0, (int)tileWidth); + Span greenPixelData = rowBuffer.GetSpan().Slice((int)tileWidth, (int)tileWidth); + Span bluePixelData = rowBuffer.GetSpan().Slice((int)(tileWidth * 2), (int)tileWidth); + Span alphaPixelData = rowBuffer.GetSpan().Slice((int)(tileWidth * 3), (int)tileWidth); + + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock); + + uint y = 0; + uint x = 0; + TPixel color = default; + for (int chunk = 0; chunk < chunks; chunk++) + { + ulong dataOffset = this.ReadUnsignedLong(stream); + long nextOffsetPosition = stream.Position; + stream.Position = (long)dataOffset; + uint tileX = this.ReadUnsignedInteger(stream); + uint tileY = this.ReadUnsignedInteger(stream); + uint levelX = this.ReadUnsignedInteger(stream); + uint levelY = this.ReadUnsignedInteger(stream); + + uint compressedBytesCount = this.ReadUnsignedInteger(stream); + decompressor.Decompress(stream, compressedBytesCount, decompressedPixelData); + + int offset = 0; + for (y = 0; y < height; y += rowsPerBlock) + { + for (x = 0; x < tilesPerScanline; x++) + { + uint rowStartIndex = tileHeight * tileY; + uint columnStartIndex = tileWidth * tileX; + for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) + { + Span pixelRow = pixels.DangerousGetRowSpan((int)rowIndex); + for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) + { + ExrChannelInfo channel = this.Channels[channelIdx]; + offset += this.ReadFloatChannelData( + stream, + channel, + decompressedPixelData.Slice(offset), + redPixelData, + greenPixelData, + bluePixelData, + alphaPixelData, + (int)tileWidth); + } + + uint columnEndIdx = (uint)Math.Min(columnStartIndex + tileWidth, width); + int channelOffset = 0; + for (int pixelRowIdx = (int)columnStartIndex; pixelRowIdx < columnEndIdx; pixelRowIdx++) + { + var pixelValue = new HalfVector4(redPixelData[channelOffset], + greenPixelData[channelOffset], bluePixelData[channelOffset], + hasAlpha ? alphaPixelData[channelOffset] : 1.0f); + color.FromVector4(pixelValue.ToVector4()); + pixelRow[pixelRowIdx] = color; + channelOffset++; + } + } + } + } + + stream.Position = nextOffsetPosition; + } + } + private int ReadFloatChannelData( BufferedReadStream stream, ExrChannelInfo channel, @@ -477,6 +580,12 @@ namespace SixLabors.ImageSharp.Formats.OpenExr case ExrConstants.AttributeNames.Tiles: header.TileXSize = this.ReadUnsignedInteger(stream); header.TileYSize = this.ReadUnsignedInteger(stream); + int mode = stream.ReadByte(); + if (mode != 0) + { + ExrThrowHelper.ThrowNotSupported("Unsupported tile mode. Only mode 0 is supported yet."); + } + break; case ExrConstants.AttributeNames.ChunkCount: header.ChunkCount = this.ReadSignedInteger(stream); @@ -614,12 +723,19 @@ namespace SixLabors.ImageSharp.Formats.OpenExr return pixelType.Value; } - private void IsSupportedCompression() + private bool IsSupportedCompression() { - if (this.Compression is not ExrCompressionType.None and not ExrCompressionType.Zips and not ExrCompressionType.Zip and not ExrCompressionType.RunLengthEncoded) + switch (this.Compression) { - ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); + case ExrCompressionType.None: + case ExrCompressionType.Zip: + case ExrCompressionType.Zips: + case ExrCompressionType.RunLengthEncoded: + case ExrCompressionType.B44: + return true; } + + return false; } private void ReadImageDataType() From a8fc4b7048c4f308bc7812f6843cf85ff2887b92 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Feb 2026 13:17:00 +1000 Subject: [PATCH 020/240] Add accumulative allocation tracking to allocators --- .../Memory/Allocators/MemoryAllocator.cs | 177 +++++++++++++++++- .../Allocators/MemoryAllocatorOptions.cs | 30 ++- .../Allocators/SimpleGcMemoryAllocator.cs | 40 +++- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 58 +++++- .../Allocators/UnmanagedMemoryAllocator.cs | 24 ++- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 8 +- .../MemoryGroup{T}.Consumed.cs | 16 +- .../MemoryGroup{T}.Owned.cs | 5 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 38 +++- .../SimpleGcMemoryAllocatorTests.cs | 44 +++++ ...niformUnmanagedPoolMemoryAllocatorTests.cs | 56 +++++- .../MemoryGroupTests.Allocate.cs | 10 +- 12 files changed, 461 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 8eaf0b6d6..2a414f869 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Memory; public abstract class MemoryAllocator { private const int OneGigabyte = 1 << 30; + private long accumulativeAllocatedBytes; + private int trackingSuppressionCount; /// /// Gets the default platform-specific global instance that @@ -23,9 +25,41 @@ public abstract class MemoryAllocator /// public static MemoryAllocator Default { get; } = Create(); - internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; + /// + /// Gets the maximum number of bytes that can be allocated by a memory group. + /// + /// + /// The allocation limit is determined by the process architecture: 4 GB for 64-bit processes and + /// 1 GB for 32-bit processes. + /// + internal long MemoryGroupAllocationLimitBytes { get; private protected set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; + + /// + /// Gets the maximum allowed total allocation size, in bytes, for the current process. + /// + /// + /// The allocation limit is determined based on the process architecture. For 64-bit processes, + /// the limit is higher than for 32-bit processes. + /// + internal long AccumulativeAllocationLimitBytes { get; private protected set; } = Environment.Is64BitProcess ? 8L * OneGigabyte : 2L * OneGigabyte; - internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; + /// + /// Gets the maximum size, in bytes, that can be allocated for a single buffer. + /// + /// + /// The single buffer allocation limit is set to 1 GB by default. + /// + internal int SingleBufferAllocationLimitBytes { get; private protected set; } = OneGigabyte; + + /// + /// Gets a value indicating whether change tracking is currently suppressed for this instance. + /// + /// + /// When change tracking is suppressed, modifications to the object will not be recorded or + /// trigger change notifications. This property is used internally to temporarily disable tracking during + /// batch updates or initialization. + /// + private bool IsTrackingSuppressed => Volatile.Read(ref this.trackingSuppressionCount) > 0; /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. @@ -53,6 +87,11 @@ public abstract class MemoryAllocator allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes); } + if (options.AccumulativeAllocationLimitMegabytes.HasValue) + { + allocator.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; + } + return allocator; } @@ -72,6 +111,10 @@ public abstract class MemoryAllocator /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. /// + /// + /// This does not dispose active allocations; callers are responsible for disposing all + /// instances to release memory. + /// public virtual void ReleaseRetainedResources() { } @@ -102,11 +145,137 @@ public abstract class MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); } - // Cast to long is safe because we already checked that the total length is within the limit. - return this.AllocateGroupCore(totalLength, (long)totalLengthInBytes, bufferAlignment, options); + long totalLengthInBytesLong = (long)totalLengthInBytes; + this.ReserveAllocation(totalLengthInBytesLong); + + using (this.SuppressTracking()) + { + try + { + MemoryGroup group = this.AllocateGroupCore(totalLength, totalLengthInBytesLong, bufferAlignment, options); + group.SetAllocationTracking(this, totalLengthInBytesLong); + return group; + } + catch + { + this.ReleaseAccumulatedBytes(totalLengthInBytesLong); + throw; + } + } } internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) where T : struct => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); + + /// + /// Tracks the allocation of an instance after reserving bytes. + /// + /// Type of the data stored in the buffer. + /// The allocation to track. + /// The allocation size in bytes. + /// The tracked allocation. + protected IMemoryOwner TrackAllocation(IMemoryOwner owner, ulong lengthInBytes) + where T : struct + { + if (this.IsTrackingSuppressed || lengthInBytes == 0) + { + return owner; + } + + return new TrackingMemoryOwner(owner, this, (long)lengthInBytes); + } + + /// + /// Reserves accumulative allocation bytes before creating the underlying buffer. + /// + /// The number of bytes to reserve. + protected void ReserveAllocation(long lengthInBytes) + { + if (this.IsTrackingSuppressed || lengthInBytes <= 0) + { + return; + } + + long total = Interlocked.Add(ref this.accumulativeAllocatedBytes, lengthInBytes); + if (total > this.AccumulativeAllocationLimitBytes) + { + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + InvalidMemoryOperationException.ThrowAllocationOverLimitException((ulong)lengthInBytes, this.AccumulativeAllocationLimitBytes); + } + } + + /// + /// Releases accumulative allocation bytes previously tracked by this allocator. + /// + /// The number of bytes to release. + internal void ReleaseAccumulatedBytes(long lengthInBytes) + { + if (lengthInBytes <= 0) + { + return; + } + + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + } + + /// + /// Suppresses accumulative allocation tracking for the lifetime of the returned scope. + /// + /// An that restores tracking when disposed. + internal IDisposable SuppressTracking() => new TrackingSuppressionScope(this); + + /// + /// Temporarily suppresses accumulative allocation tracking within a scope. + /// + private sealed class TrackingSuppressionScope : IDisposable + { + private MemoryAllocator? allocator; + + public TrackingSuppressionScope(MemoryAllocator allocator) + { + this.allocator = allocator; + _ = Interlocked.Increment(ref allocator.trackingSuppressionCount); + } + + public void Dispose() + { + if (this.allocator != null) + { + _ = Interlocked.Decrement(ref this.allocator.trackingSuppressionCount); + this.allocator = null; + } + } + } + + /// + /// Wraps an to release accumulative tracking on dispose. + /// + private sealed class TrackingMemoryOwner : IMemoryOwner + where T : struct + { + private IMemoryOwner? owner; + private readonly MemoryAllocator allocator; + private readonly long lengthInBytes; + + public TrackingMemoryOwner(IMemoryOwner owner, MemoryAllocator allocator, long lengthInBytes) + { + this.owner = owner; + this.allocator = allocator; + this.lengthInBytes = lengthInBytes; + } + + public Memory Memory => this.owner?.Memory ?? Memory.Empty; + + public void Dispose() + { + // Ensure only one caller disposes the inner owner and releases the reservation. + IMemoryOwner? inner = Interlocked.Exchange(ref this.owner, null); + if (inner != null) + { + inner.Dispose(); + this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes); + } + } + } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index d9ba62c1e..3339203da 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -10,6 +10,7 @@ public struct MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; private int? allocationLimitMegabytes; + private int? accumulativeAllocationLimitMegabytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -17,7 +18,7 @@ public struct MemoryAllocatorOptions /// public int? MaximumPoolSizeMegabytes { - get => this.maximumPoolSizeMegabytes; + readonly get => this.maximumPoolSizeMegabytes; set { if (value.HasValue) @@ -35,7 +36,7 @@ public struct MemoryAllocatorOptions /// public int? AllocationLimitMegabytes { - get => this.allocationLimitMegabytes; + readonly get => this.allocationLimitMegabytes; set { if (value.HasValue) @@ -46,4 +47,29 @@ public struct MemoryAllocatorOptions this.allocationLimitMegabytes = value; } } + + /// + /// Gets or sets a value defining the maximum total size that can be allocated by the allocator in Megabytes. + /// means platform default: 2GB on 32-bit processes, 8GB on 64-bit processes. + /// + public int? AccumulativeAllocationLimitMegabytes + { + readonly get => this.accumulativeAllocationLimitMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AccumulativeAllocationLimitMegabytes)); + if (this.AllocationLimitMegabytes.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo( + value.Value, + this.AllocationLimitMegabytes.Value, + nameof(this.AccumulativeAllocationLimitMegabytes)); + } + } + + this.accumulativeAllocationLimitMegabytes = value; + } + } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 675afe8b9..9cbb15713 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,6 +12,32 @@ namespace SixLabors.ImageSharp.Memory; /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { + /// + /// Initializes a new instance of the class with default limits. + /// + public SimpleGcMemoryAllocator() + : this(default) + { + } + + /// + /// Initializes a new instance of the class with custom limits. + /// + /// The to apply. + public SimpleGcMemoryAllocator(MemoryAllocatorOptions options) + { + if (options.AllocationLimitMegabytes.HasValue) + { + this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; + this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes); + } + + if (options.AccumulativeAllocationLimitMegabytes.HasValue) + { + this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; + } + } + /// protected internal override int GetBufferCapacityInBytes() => int.MaxValue; @@ -29,6 +55,18 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); } - return new BasicArrayBuffer(new T[length]); + long lengthInBytesLong = (long)lengthInBytes; + this.ReserveAllocation(lengthInBytesLong); + + try + { + IMemoryOwner buffer = new BasicArrayBuffer(new T[length]); + return this.TrackAllocation(buffer, lengthInBytes); + } + catch + { + this.ReleaseAccumulatedBytes(lengthInBytesLong); + throw; + } } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 10defe6cd..28554b589 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -96,13 +96,24 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes) { - SharedArrayPoolBuffer buffer = new(length); - if (options.Has(AllocationOptions.Clean)) + long lengthInBytesLong = (long)lengthInBytes; + this.ReserveAllocation(lengthInBytesLong); + + try { - buffer.GetSpan().Clear(); - } + SharedArrayPoolBuffer buffer = new(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } - return buffer; + return this.TrackAllocation(buffer, lengthInBytes); + } + catch + { + this.ReleaseAccumulatedBytes(lengthInBytesLong); + throw; + } } if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes) @@ -110,12 +121,38 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return buffer; + long lengthInBytesLong = (long)lengthInBytes; + this.ReserveAllocation(lengthInBytesLong); + + try + { + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); + return this.TrackAllocation(buffer, lengthInBytes); + } + catch + { + this.ReleaseAccumulatedBytes(lengthInBytesLong); + throw; + } } } - return this.nonPoolAllocator.Allocate(length, options); + long nonPooledLengthInBytesLong = (long)lengthInBytes; + this.ReserveAllocation(nonPooledLengthInBytesLong); + + try + { + using (this.nonPoolAllocator.SuppressTracking()) + { + IMemoryOwner nonPooled = this.nonPoolAllocator.Allocate(length, options); + return this.TrackAllocation(nonPooled, lengthInBytes); + } + } + catch + { + this.ReleaseAccumulatedBytes(nonPooledLengthInBytesLong); + throw; + } } /// @@ -148,7 +185,10 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato return poolGroup; } - return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); + using (this.nonPoolAllocator.SuppressTracking()) + { + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); + } } public override void ReleaseRetainedResources() => this.pool.Release(); diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index daf1a7992..73cef8b5e 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory; @@ -19,13 +20,26 @@ internal class UnmanagedMemoryAllocator : MemoryAllocator protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + where T : struct { - UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); - if (options.Has(AllocationOptions.Clean)) + ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); + long lengthInBytesLong = (long)lengthInBytes; + this.ReserveAllocation(lengthInBytesLong); + + try { - buffer.GetSpan().Clear(); - } + UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } - return buffer; + return this.TrackAllocation(buffer, lengthInBytes); + } + catch + { + this.ReleaseAccumulatedBytes(lengthInBytesLong); + throw; + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 7e9719ea7..03f26aab0 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -15,12 +15,12 @@ public interface IMemoryGroup : IReadOnlyList> /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. /// - int BufferLength { get; } + public int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - long TotalLength { get; } + public long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. @@ -29,7 +29,7 @@ public interface IMemoryGroup : IReadOnlyList> /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces /// the image buffers internally. /// - bool IsValid { get; } + public bool IsValid { get; } /// /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current @@ -39,5 +39,5 @@ public interface IMemoryGroup : IReadOnlyList> /// implementation, which is still available when casting to one of the underlying interfaces. /// /// A new instance mapping the current values in use. - new MemoryGroupEnumerator GetEnumerator(); + public new MemoryGroupEnumerator GetEnumerator(); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 950e2a019..75e93ce7f 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -31,23 +31,23 @@ internal abstract partial class MemoryGroup /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } + public override MemoryGroupEnumerator GetEnumerator() => new(this); /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* The runtime sees the Array class as if it implemented the * type-generic collection interfaces explicitly, so here we * can just cast the source array to IList> (or to * an equivalent type), and invoke the generic GetEnumerator * method directly from that interface reference. This saves * having to create our own iterator block here. */ - return ((IList>)this.source).GetEnumerator(); - } + => ((IList>)this.source).GetEnumerator(); - public override void Dispose() => this.View.Invalidate(); + public override void Dispose() + { + this.View.Invalidate(); + this.ReleaseAllocationTracking(); + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index af896ee0e..be92272bb 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -73,8 +73,8 @@ internal abstract partial class MemoryGroup result[i] = currentBuffer; } - ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); - result[result.Length - 1] = lastBuffer; + ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[^1], sizeOfLastBuffer, options); + result[^1] = lastBuffer; return result; } @@ -155,6 +155,7 @@ internal abstract partial class MemoryGroup } } + this.ReleaseAllocationTracking(); this.memoryOwners = null; this.IsValid = false; this.groupLifetimeGuard = null; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 6dd99fcb0..aa0e18895 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -22,6 +22,9 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable private static readonly int ElementSize = Unsafe.SizeOf(); private MemoryGroupSpanCache memoryGroupSpanCache; + private MemoryAllocator? trackingAllocator; + private long trackingLengthInBytes; + private int trackingReleased; private MemoryGroup(int bufferLength, long totalLength) { @@ -52,16 +55,45 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// public abstract MemoryGroupEnumerator GetEnumerator(); + /// + /// Configures allocation tracking by specifying the allocator and the length, in bytes, to be tracked. + /// + /// The memory allocator to use for tracking allocations. + /// The length, in bytes, of the memory region to track. Must be greater than or equal to zero. + /// + /// Intended for initialization; callers should avoid changing tracking state concurrently with disposal. + /// + internal void SetAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + { + this.trackingAllocator = allocator; + this.trackingLengthInBytes = lengthInBytes; + } + + /// + /// Releases any resources or tracking information associated with allocation tracking for this instance. + /// + /// + /// This method is intended to be called when allocation tracking is no longer needed. It is safe + /// to call multiple times; subsequent calls after the first have no effect, even when called concurrently. + /// + internal void ReleaseAllocationTracking() + { + if (Interlocked.Exchange(ref this.trackingReleased, 1) == 0 && this.trackingAllocator != null) + { + this.trackingAllocator.ReleaseAccumulatedBytes(this.trackingLengthInBytes); + this.trackingAllocator = null; + } + } + /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* This method is implemented in each derived class. * Implementing the method here as non-abstract and throwing, * then reimplementing it explicitly in each derived class, is * a workaround for the lack of support for abstract explicit * interface method implementations in C#. */ - throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); - } + => throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 0e791c5d9..c33e6d107 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -48,6 +48,50 @@ public class SimpleGcMemoryAllocatorTests } } + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + [StructLayout(LayoutKind.Explicit, Size = 512)] private struct BigStruct { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index c1f5b44bf..f6cbae382 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -16,8 +16,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests { public class BufferTests1 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 1024, poolBufferSizeInBytes: 2048, maxPoolSizeInBytes: 2048 * 4, @@ -31,8 +31,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests public class BufferTests2 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 512, poolBufferSizeInBytes: 1024, maxPoolSizeInBytes: 1024 * 4, @@ -179,8 +179,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g1.Dispose(); // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); - IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy1 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); + IntPtr dummy2 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); @@ -446,6 +446,50 @@ public class UniformUnmanagedPoolMemoryAllocatorTests Assert.Throws(() => allocator.AllocateGroup(5 * oneMb, 1024)); } + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] public void MemoryAllocator_Create_SetHighLimit() { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 678a089a8..cca2230e6 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -98,7 +98,15 @@ public partial class MemoryGroupTests [InlineData(AllocationOptions.Clean)] public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) { - UniformUnmanagedMemoryPool pool = new(10, 5); + // Disable trimming to avoid buffers being freed between Return and TryAllocate by the + // trim timer or the Gen2 GC callback. + UniformUnmanagedMemoryPool pool = new( + 10, + 5, + new UniformUnmanagedMemoryPool.TrimSettings + { + Rate = 0 + }); UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { From 997c431a1ecc8875aa2ceeaf42b92460374f6d85 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Feb 2026 16:06:02 +1000 Subject: [PATCH 021/240] Update code-coverage.yml --- .github/workflows/code-coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index a7278a817..07ce0408f 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -93,7 +93,8 @@ jobs: path: tests/Images/ActualOutput/ - name: Codecov Update - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} From 182874ecfccd95125e1cf4c58d90d788b8fd5b45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:50:39 +0000 Subject: [PATCH 022/240] Bump actions/upload-artifact from 4 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 2 +- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ff3d5c7b0..020b667e6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -209,7 +209,7 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 07ce0408f..ce75a2781 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -86,7 +86,7 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip From 7868f55bc722d4785bbe68b03194e6f931ecf2d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:50:44 +0000 Subject: [PATCH 023/240] Bump actions/cache from 4 to 5 Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 8 ++++---- .github/workflows/code-coverage.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ff3d5c7b0..796074776 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -49,7 +49,7 @@ jobs: run: echo "lfs_key=$LFS_KEY" >> "$GITHUB_OUTPUT" - name: Git Setup LFS Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .git/lfs key: ${{ steps.expose-key.outputs.lfs_key }} @@ -144,7 +144,7 @@ jobs: # Use the warmed key from WarmLFS. Do not recompute or recreate .lfs-assets-id here. - name: Git Setup LFS Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .git/lfs key: ${{ needs.WarmLFS.outputs.lfs_key }} @@ -157,7 +157,7 @@ jobs: uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: nuget-cache with: path: ~/.nuget @@ -236,7 +236,7 @@ jobs: uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: nuget-cache with: path: ~/.nuget diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 07ce0408f..1c794ff64 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -46,7 +46,7 @@ jobs: run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id - name: Git Setup LFS Cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: lfs-cache with: path: .git/lfs @@ -59,7 +59,7 @@ jobs: uses: NuGet/setup-nuget@v2 - name: NuGet Setup Cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: nuget-cache with: path: ~/.nuget From 54808ea21a6df5d336a9bc94a31ed58e78ff3c64 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Tue, 17 Feb 2026 17:15:55 +0100 Subject: [PATCH 024/240] Undo #2025 --- ...niformUnmanagedMemoryPoolMemoryAllocator.cs | 18 ++++-------------- ...UniformUnmanagedPoolMemoryAllocatorTests.cs | 13 ------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 10defe6cd..d5cd329f1 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -71,10 +71,6 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); } - // This delegate allows overriding the method returning the available system memory, - // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466 - internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; - /// protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; @@ -155,20 +151,14 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato private static long GetDefaultMaxPoolSizeBytes() { - // On 64 bit set the pool size to a portion of the total available memory. - // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 if (Environment.Is64BitProcess) { - long total = GetTotalAvailableMemoryBytes(); - - // Workaround for https://github.com/dotnet/runtime/issues/65466 - if (total > 0) - { - return (long)((ulong)total / 8); - } + // On 64 bit set the pool size to a portion of the total available memory. + GCMemoryInfo info = GC.GetGCMemoryInfo(); + return info.TotalAvailableMemoryBytes / 8; } - // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: + // Stick to a conservative value of 128 Megabytes on 32 bit. return 128 * OneMegabyte; } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index c1f5b44bf..1d185a0de 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -409,19 +409,6 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g = null; } - [Fact] - public void Issue2001_NegativeMemoryReportedByGc() - { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466 - UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176; - _ = MemoryAllocator.Create(); - } - } - [Fact] public void Allocate_OverLimit_ThrowsInvalidMemoryOperationException() { From 56fc139b06182d92fc6b01998daca88e46cb167d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 19 Feb 2026 15:07:00 +1000 Subject: [PATCH 025/240] Don't have an affective default limit. --- src/ImageSharp/Memory/Allocators/MemoryAllocator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 2a414f869..d6badf928 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -38,10 +38,13 @@ public abstract class MemoryAllocator /// Gets the maximum allowed total allocation size, in bytes, for the current process. /// /// - /// The allocation limit is determined based on the process architecture. For 64-bit processes, - /// the limit is higher than for 32-bit processes. + /// Defaults to , effectively imposing no limit on total allocations. + /// This property can be set to enforce a cap on total memory usage across all allocations made through this allocator instance, providing + /// a safeguard against excessive memory consumption.
+ /// When the cumulative size of active allocations exceeds this limit, an will be thrown to + /// prevent further allocations and signal that the limit has been breached. ///
- internal long AccumulativeAllocationLimitBytes { get; private protected set; } = Environment.Is64BitProcess ? 8L * OneGigabyte : 2L * OneGigabyte; + internal long AccumulativeAllocationLimitBytes { get; private protected set; } = long.MaxValue; /// /// Gets the maximum size, in bytes, that can be allocated for a single buffer. From 17699d1953b54ac29ade9d8fc876aa97fcb9027d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Mar 2026 17:48:55 +1000 Subject: [PATCH 026/240] Add row-stride overloads for memory APIs --- src/ImageSharp/Image.LoadPixelData.cs | 120 +++- src/ImageSharp/Image.WrapMemory.cs | 534 ++++++++++++------ src/ImageSharp/ImageFrame.LoadPixelData.cs | 63 ++- src/ImageSharp/ImageFrame.cs | 2 +- .../ImageFrameCollection{TPixel}.cs | 9 +- src/ImageSharp/ImageFrame{TPixel}.cs | 99 ++-- src/ImageSharp/Image{TPixel}.cs | 53 +- src/ImageSharp/Memory/Buffer2DExtensions.cs | 6 +- src/ImageSharp/Memory/Buffer2DRegion{T}.cs | 12 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 229 +++++++- .../MemoryGroupExtensions.cs | 191 ++++--- .../Memory/MemoryAllocatorExtensions.cs | 6 + ...ransformItemsDelegate{TSource, TTarget}.cs | 8 - .../Memory/TransformItemsInplaceDelegate.cs | 6 - .../Transforms/CropProcessor{TPixel}.cs | 2 +- .../Linear/RotateProcessor{TPixel}.cs | 2 +- .../Resize/ResizeProcessor{TPixel}.cs | 2 +- .../ImageFrameCollectionTests.Generic.cs | 2 +- .../Image/ImageTests.LoadPixelData.cs | 74 +++ .../Image/ImageTests.WrapMemory.cs | 97 ++++ .../Memory/Buffer2DTests.CopyTo.cs | 43 ++ .../Memory/Buffer2DTests.SwapOrCopyContent.cs | 69 +++ .../Memory/Buffer2DTests.WrapMemory.cs | 91 +++ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 46 +- .../MemoryGroupTests.CopyTo.cs | 126 ++--- .../DiscontiguousBuffers/MemoryGroupTests.cs | 78 --- 26 files changed, 1438 insertions(+), 532 deletions(-) delete mode 100644 src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs delete mode 100644 src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs create mode 100644 tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs create mode 100644 tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index efe1b6e2f..2847f0d57 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp; @@ -25,6 +24,27 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Create a new instance of the class from raw data + /// using pixels between source row starts. + /// + /// The readonly span containing image data. + /// The width of the final image. + /// The height of the final image. + /// The number of pixels between row starts in . + /// The pixel format. + /// + /// or is not positive, + /// or is less than . + /// + /// + /// is smaller than ((height - 1) * rowStride) + width. + /// + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height, int rowStride) + where TPixel : unmanaged, IPixel + => LoadPixelData(Configuration.Default, data, width, height, rowStride); + /// /// Create a new instance of the class from the given readonly span of bytes in format. /// @@ -38,6 +58,28 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Create a new instance of the class from a readonly span of bytes in + /// format using bytes between source row starts. + /// + /// The readonly span containing image data. + /// The width of the final image. + /// The height of the final image. + /// The number of bytes between row starts in . + /// The pixel format. + /// + /// or is not positive, + /// or resolves to fewer than pixels. + /// + /// + /// is not divisible by the pixel size, + /// or is smaller than the required strided image length. + /// + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height, int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => LoadPixelData(Configuration.Default, data, width, height, rowStrideInBytes); + /// /// Create a new instance of the class from the given readonly span of bytes in format. /// @@ -53,6 +95,40 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + /// + /// Create a new instance of the class from a readonly span of bytes in + /// format using bytes between source row starts. + /// + /// The configuration for the decoder. + /// The readonly span containing image data. + /// The width of the final image. + /// The height of the final image. + /// The number of bytes between row starts in . + /// The pixel format. + /// The configuration is null. + /// + /// or is not positive, + /// or resolves to fewer than pixels. + /// + /// + /// is not divisible by the pixel size, + /// or is smaller than the required strided image length. + /// + /// A new . + public static Image LoadPixelData( + Configuration configuration, + ReadOnlySpan data, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + + int rowStride = GetPixelRowStrideFromByteStride(width, rowStrideInBytes, nameof(rowStrideInBytes)); + return LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height, rowStride); + } + /// /// Create a new instance of the class from the raw data. /// @@ -66,20 +142,42 @@ public abstract partial class Image /// A new . public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, data, width, height, width); + + /// + /// Create a new instance of the class from raw data + /// using pixels between source row starts. + /// + /// The configuration for the decoder. + /// The readonly span containing the image pixel data. + /// The width of the final image. + /// The height of the final image. + /// The number of pixels between row starts in . + /// The configuration is null. + /// + /// or is not positive, + /// or is less than . + /// + /// + /// is smaller than ((height - 1) * rowStride) + width. + /// + /// The pixel format. + /// A new . + public static Image LoadPixelData( + Configuration configuration, + ReadOnlySpan data, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); - - if (data.IsEmpty) - { - throw new ArgumentException("Pixel data cannot be empty.", nameof(data)); - } - - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + ValidateWrapMemoryStride(width, height, rowStride, nameof(rowStride)); + long requiredLength = GetRequiredLength(width, height, rowStride); + Guard.MustBeGreaterThanOrEqualTo(data.Length, requiredLength, nameof(data)); Image image = new(configuration, width, height); - data = data[..count]; - data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); + image.Frames.RootFrame.PixelBuffer.CopyFrom(data, rowStride); return image; } diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 03bec8bc6..e44af5d0d 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -58,29 +59,55 @@ public abstract partial class Image /// /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. + /// Wraps an existing memory area allowing viewing/manipulation as an + /// with pixels between row starts. /// /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. Consumers must ensure that + /// the input buffer remains valid for the full lifetime of the returned image. /// /// - /// The pixel type - /// The - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. + /// The pixel type. + /// The . + /// The source pixel memory. + /// The width of the memory image in pixels. + /// The height of the memory image in pixels. + /// The number of pixels between row starts in . + /// The . /// The configuration is null. + /// The metadata is null. + /// + /// or is not positive, + /// or is less than . + /// + /// + /// The length of is less than + /// ((height - 1) * rowStride) + width. + /// /// An instance. + public static Image WrapMemory( + Configuration configuration, + Memory pixelMemory, + int width, + int height, + int rowStride, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + ValidateWrapMemoryStride(width, height, rowStride, nameof(rowStride)); + + long requiredLength = GetRequiredLength(width, height, rowStride); + Guard.IsTrue(pixelMemory.Length >= requiredLength, nameof(pixelMemory), "The length of the input memory is less than the specified image size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemory); + return new Image(configuration, memorySource, width, height, rowStride, metadata); + } + + /// public static Image WrapMemory( Configuration configuration, Memory pixelMemory, @@ -89,29 +116,17 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. + /// + public static Image WrapMemory( + Configuration configuration, + Memory pixelMemory, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemory, width, height, rowStride, new ImageMetadata()); + + /// public static Image WrapMemory( Memory pixelMemory, int width, @@ -119,6 +134,15 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, pixelMemory, width, height); + /// + public static Image WrapMemory( + Memory pixelMemory, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemory, width, height, rowStride); + /// /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, /// allowing to view/manipulate it as an instance. @@ -152,19 +176,55 @@ public abstract partial class Image } /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. + /// + /// Wraps an existing memory owner allowing viewing/manipulation as an + /// with pixels between row starts. + /// + /// + /// Ownership of is transferred to the returned image. The caller + /// must not dispose the owner manually. + /// /// /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. + /// The . + /// The pixel memory owner transferred to the image. + /// The width of the memory image in pixels. + /// The height of the memory image in pixels. + /// The number of pixels between row starts in the source memory. + /// The . /// The configuration is null. - /// An instance + /// The metadata is null. + /// + /// or is not positive, + /// or is less than . + /// + /// + /// The length of is less than + /// ((height - 1) * rowStride) + width. + /// + /// An instance. + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner pixelMemoryOwner, + int width, + int height, + int rowStride, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + ValidateWrapMemoryStride(width, height, rowStride, nameof(rowStride)); + + long requiredLength = GetRequiredLength(width, height, rowStride); + Guard.IsTrue(pixelMemoryOwner.Memory.Length >= requiredLength, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, rowStride, metadata); + } + + /// public static Image WrapMemory( Configuration configuration, IMemoryOwner pixelMemoryOwner, @@ -173,18 +233,17 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. + /// + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner pixelMemoryOwner, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemoryOwner, width, height, rowStride, new ImageMetadata()); + + /// public static Image WrapMemory( IMemoryOwner pixelMemoryOwner, int width, @@ -192,6 +251,15 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); + /// + public static Image WrapMemory( + IMemoryOwner pixelMemoryOwner, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height, rowStride); + /// /// /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as @@ -240,29 +308,56 @@ public abstract partial class Image /// /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. + /// Wraps an existing byte memory area allowing viewing/manipulation as an + /// with bytes between row starts. /// /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. Consumers must ensure that + /// the input buffer remains valid for the full lifetime of the returned image. /// /// - /// The pixel type - /// The - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. + /// The pixel type. + /// The . + /// The source byte memory. + /// The width of the memory image in pixels. + /// The height of the memory image in pixels. + /// The number of bytes between row starts in . + /// The . /// The configuration is null. + /// The metadata is null. + /// + /// or is not positive, + /// or resolves to less than pixels. + /// + /// + /// is not divisible by the size of , + /// or is smaller than the required strided image length. + /// /// An instance. + public static Image WrapMemory( + Configuration configuration, + Memory byteMemory, + int width, + int height, + int rowStrideInBytes, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + int rowStride = GetPixelRowStrideFromByteStride(width, rowStrideInBytes, nameof(rowStrideInBytes)); + long requiredLength = GetRequiredLength(width, height, rowStride); + + ByteMemoryManager memoryManager = new(byteMemory); + Guard.IsTrue(memoryManager.Memory.Length >= requiredLength, nameof(byteMemory), "The length of the input memory is less than the specified image size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); + return new Image(configuration, memorySource, width, height, rowStride, metadata); + } + + /// public static Image WrapMemory( Configuration configuration, Memory byteMemory, @@ -271,29 +366,17 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(configuration, byteMemory, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. + /// + public static Image WrapMemory( + Configuration configuration, + Memory byteMemory, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, byteMemory, width, height, rowStrideInBytes, new ImageMetadata()); + + /// public static Image WrapMemory( Memory byteMemory, int width, @@ -301,6 +384,15 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, byteMemory, width, height); + /// + public static Image WrapMemory( + Memory byteMemory, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, byteMemory, width, height, rowStrideInBytes); + /// /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, /// allowing to view/manipulate it as an instance. @@ -337,19 +429,56 @@ public abstract partial class Image } /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. + /// + /// Wraps an existing byte memory owner allowing viewing/manipulation as an + /// with bytes between row starts. + /// + /// + /// Ownership of is transferred to the returned image. The caller + /// must not dispose the owner manually. + /// /// /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. + /// The . + /// The byte memory owner transferred to the image. + /// The width of the memory image in pixels. + /// The height of the memory image in pixels. + /// The number of bytes between row starts in the source memory. + /// The . /// The configuration is null. - /// An instance + /// The metadata is null. + /// + /// or is not positive, + /// or resolves to less than pixels. + /// + /// + /// is not divisible by the size of , + /// or is smaller than the required strided image length. + /// + /// An instance. + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner byteMemoryOwner, + int width, + int height, + int rowStrideInBytes, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + int rowStride = GetPixelRowStrideFromByteStride(width, rowStrideInBytes, nameof(rowStrideInBytes)); + + ByteMemoryOwner pixelMemoryOwner = new(byteMemoryOwner); + long requiredLength = GetRequiredLength(width, height, rowStride); + Guard.IsTrue(pixelMemoryOwner.Memory.Length >= requiredLength, nameof(byteMemoryOwner), "The length of the input memory is less than the specified image size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, rowStride, metadata); + } + + /// public static Image WrapMemory( Configuration configuration, IMemoryOwner byteMemoryOwner, @@ -358,18 +487,17 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(configuration, byteMemoryOwner, width, height, new ImageMetadata()); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. + /// + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner byteMemoryOwner, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, byteMemoryOwner, width, height, rowStrideInBytes, new ImageMetadata()); + + /// public static Image WrapMemory( IMemoryOwner byteMemoryOwner, int width, @@ -377,6 +505,15 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, byteMemoryOwner, width, height); + /// + public static Image WrapMemory( + IMemoryOwner byteMemoryOwner, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, byteMemoryOwner, width, height, rowStrideInBytes); + /// /// /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as @@ -434,35 +571,60 @@ public abstract partial class Image /// /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. + /// Wraps an unmanaged memory area allowing viewing/manipulation as an + /// with bytes between row starts. /// /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. + /// Callers must ensure the memory referenced by remains valid for the full + /// lifetime of the returned image. /// /// - /// The pixel type - /// The - /// The pointer to the target memory buffer to wrap. - /// The byte length of the memory allocated. - /// The width of the memory image. - /// The height of the memory image. + /// The pixel type. + /// The . + /// The pointer to the source memory. + /// The byte length of the source memory. + /// The width of the memory image in pixels. + /// The height of the memory image in pixels. + /// The number of bytes between row starts in the source memory. + /// The . /// The configuration is null. + /// The metadata is null. + /// + /// is null, + /// is not divisible by the size of , + /// or is smaller than the required strided image length. + /// + /// + /// or is not positive, + /// or resolves to less than pixels. + /// /// An instance. + public static unsafe Image WrapMemory( + Configuration configuration, + void* pointer, + int bufferSizeInBytes, + int width, + int height, + int rowStrideInBytes, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null"); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + int rowStride = GetPixelRowStrideFromByteStride(width, rowStrideInBytes, nameof(rowStrideInBytes)); + long requiredLength = GetRequiredLength(width, height, rowStride); + + Guard.MustBeLessThanOrEqualTo(requiredLength, int.MaxValue, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(bufferSizeInBytes / Unsafe.SizeOf(), requiredLength, nameof(bufferSizeInBytes)); + + UnmanagedMemoryManager memoryManager = new(pointer, (int)requiredLength); + MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); + return new Image(configuration, memorySource, width, height, rowStride, metadata); + } + + /// public static unsafe Image WrapMemory( Configuration configuration, void* pointer, @@ -472,35 +634,18 @@ public abstract partial class Image where TPixel : unmanaged, IPixel => WrapMemory(configuration, pointer, bufferSizeInBytes, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type. - /// The pointer to the target memory buffer to wrap. - /// The byte length of the memory allocated. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. + /// + public static unsafe Image WrapMemory( + Configuration configuration, + void* pointer, + int bufferSizeInBytes, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pointer, bufferSizeInBytes, width, height, rowStrideInBytes, new ImageMetadata()); + + /// public static unsafe Image WrapMemory( void* pointer, int bufferSizeInBytes, @@ -508,4 +653,41 @@ public abstract partial class Image int height) where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, pointer, bufferSizeInBytes, width, height); + + /// + public static unsafe Image WrapMemory( + void* pointer, + int bufferSizeInBytes, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pointer, bufferSizeInBytes, width, height, rowStrideInBytes); + + private static void ValidateWrapMemoryStride(int width, int height, int rowStride, string rowStrideParamName) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(rowStride, width, rowStrideParamName); + } + + private static int GetPixelRowStrideFromByteStride(int width, int rowStrideInBytes, string rowStrideParamName) + where TPixel : unmanaged, IPixel + { + int pixelSizeInBytes = Unsafe.SizeOf(); + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(rowStrideInBytes, 0, rowStrideParamName); + Guard.IsTrue( + rowStrideInBytes % pixelSizeInBytes == 0, + rowStrideParamName, + "The row stride in bytes must be divisible by the pixel size."); + + int rowStride = rowStrideInBytes / pixelSizeInBytes; + Guard.MustBeGreaterThanOrEqualTo(rowStride, width, rowStrideParamName); + return rowStride; + } + + private static long GetRequiredLength(int width, int height, int rowStride) + => checked(((long)(height - 1) * rowStride) + width); } diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 003a0349a..6e2658b6e 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -25,6 +26,36 @@ public partial class ImageFrame where TPixel : unmanaged, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The number of bytes between row starts in . + /// The pixel format. + /// A new . + internal static ImageFrame LoadPixelData( + Configuration configuration, + ReadOnlySpan data, + int width, + int height, + int rowStrideInBytes) + where TPixel : unmanaged, IPixel + { + int pixelSizeInBytes = Unsafe.SizeOf(); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(rowStrideInBytes, 0, nameof(rowStrideInBytes)); + Guard.IsTrue( + rowStrideInBytes % pixelSizeInBytes == 0, + nameof(rowStrideInBytes), + "The row stride in bytes must be divisible by the pixel size."); + + int rowStride = rowStrideInBytes / pixelSizeInBytes; + return LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height, rowStride); + } + /// /// Create a new instance of the class from the raw data. /// @@ -36,14 +67,36 @@ public partial class ImageFrame /// A new . internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, data, width, height, width); + + /// + /// Create a new instance of the class from raw data + /// using pixels between source row starts. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The span containing the image pixel data. + /// The width of the final image. + /// The height of the final image. + /// The number of pixels between row starts in . + /// The pixel format. + /// A new . + internal static ImageFrame LoadPixelData( + Configuration configuration, + ReadOnlySpan data, + int width, + int height, + int rowStride) + where TPixel : unmanaged, IPixel { - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride)); - ImageFrame image = new(configuration, width, height); + long requiredLength = checked(((long)(height - 1) * rowStride) + width); + Guard.MustBeGreaterThanOrEqualTo(data.Length, requiredLength, nameof(data)); - data = data[..count]; - data.CopyTo(image.PixelBuffer.FastMemoryGroup); + ImageFrame image = new(configuration, width, height); + image.PixelBuffer.CopyFrom(data, rowStride); return image; } diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 3d4b63fad..cd6fa6d98 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -71,7 +71,7 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable /// Whether to dispose of managed and unmanaged objects. protected abstract void Dispose(bool disposing); - internal abstract void CopyPixelsTo(MemoryGroup destination) + internal abstract void CopyPixelsTo(Buffer2D destination) where TDestinationPixel : unmanaged, IPixel; /// diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index ad7d71974..892adb7aa 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -27,11 +27,16 @@ public sealed class ImageFrameCollection : ImageFrameCollection, IEnumer } internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) + : this(parent, width, height, width, memorySource) + { + } + + internal ImageFrameCollection(Image parent, int width, int height, int rowStride, MemoryGroup memorySource) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.Configuration, width, height, memorySource)); + this.frames.Add(new ImageFrame(parent.Configuration, width, height, rowStride, memorySource)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -416,7 +421,7 @@ public sealed class ImageFrameCollection : ImageFrameCollection, IEnumer this.parent.Configuration, source.Size, source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); + source.CopyPixelsTo(result.PixelBuffer); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index de71e77ca..4e25c4e58 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -114,7 +114,20 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// The height of the image in pixels. /// The memory source. internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) - : this(configuration, width, height, memorySource, new ImageFrameMetadata()) + : this(configuration, width, height, width, memorySource, new ImageFrameMetadata()) + { + } + + /// + /// Initializes a new instance of the class wrapping an existing buffer. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The number of elements between row starts. + /// The memory source. + internal ImageFrame(Configuration configuration, int width, int height, int rowStride, MemoryGroup memorySource) + : this(configuration, width, height, rowStride, memorySource, new ImageFrameMetadata()) { } @@ -127,12 +140,26 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// The memory source. /// The metadata. internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) + : this(configuration, width, height, width, memorySource, metadata) + { + } + + /// + /// Initializes a new instance of the class wrapping an existing buffer. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The number of elements between row starts. + /// The memory source. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, int rowStride, MemoryGroup memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = new Buffer2D(memorySource, width, height); + this.PixelBuffer = new Buffer2D(memorySource, width, height, rowStride); } /// @@ -150,7 +177,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource source.PixelBuffer.Width, source.PixelBuffer.Height, configuration.PreferContiguousImageBuffers); - source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); + source.PixelBuffer.CopyTo(this.PixelBuffer); } /// @@ -270,16 +297,23 @@ public sealed class ImageFrame : ImageFrame, IPixelSource } /// - /// Copy image pixels to . + /// Copy image pixels to using the backing row layout. /// + /// + /// Destination length must be at least ((Height - 1) * PixelBuffer.RowStride) + Width. + /// /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + public void CopyPixelDataTo(Span destination) => this.PixelBuffer.CopyTo(destination); /// - /// Copy image pixels to . + /// Copy image pixels to using the backing row layout. /// + /// + /// Destination length must be at least + /// (((Height - 1) * PixelBuffer.RowStride) + Width) * sizeof(TPixel) bytes. + /// /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + public void CopyPixelDataTo(Span destination) => this.PixelBuffer.CopyTo(MemoryMarshal.Cast(destination)); /// /// Gets the representation of the pixels as a in the source image's pixel format @@ -294,17 +328,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// The referencing the image buffer. /// The indicating the success. public bool DangerousTryGetSinglePixelMemory(out Memory memory) - { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) - { - memory = default; - return false; - } - - memory = mg.Single(); - return true; - } + => this.PixelBuffer.DangerousTryGetSingleMemory(out memory); /// /// Gets a reference to the pixel at the specified position. @@ -327,7 +351,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); + this.PixelBuffer.CopyTo(target); } /// @@ -370,20 +394,32 @@ public sealed class ImageFrame : ImageFrame, IPixelSource this.isDisposed = true; } - internal override void CopyPixelsTo(MemoryGroup destination) + internal override void CopyPixelsTo(Buffer2D destination) { + Guard.NotNull(destination, nameof(destination)); + Guard.IsTrue( + destination.Width == this.Width && destination.Height == this.Height, + nameof(destination), + "Destination buffer must have the same dimensions as the source frame."); + if (typeof(TPixel) == typeof(TDestinationPixel)) { - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + for (int y = 0; y < this.Height; y++) { - Span d1 = MemoryMarshal.Cast(d); - s.CopyTo(d1); - }); + Span sourceRow = this.PixelBuffer.DangerousGetRowSpan(y); + Span destinationRow = destination.DangerousGetRowSpan(y); + sourceRow.CopyTo(MemoryMarshal.Cast(destinationRow)); + } + return; } - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) - => PixelOperations.Instance.To(this.Configuration, s, d)); + for (int y = 0; y < this.Height; y++) + { + Span sourceRow = this.PixelBuffer.DangerousGetRowSpan(y); + Span destinationRow = destination.DangerousGetRowSpan(y); + PixelOperations.Instance.To(this.Configuration, sourceRow, destinationRow); + } } /// @@ -441,16 +477,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// The value to initialize the bitmap with. internal void Clear(TPixel value) { - MemoryGroup group = this.PixelBuffer.FastMemoryGroup; - - if (value.Equals(default)) - { - group.Clear(); - } - else - { - group.Fill(value); - } + this.PixelBuffer.Clear(value); } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index dff8f577f..18db34356 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -91,7 +91,7 @@ public sealed class Image : Image Configuration configuration, Buffer2D pixelBuffer, ImageMetadata metadata) - : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) + : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, pixelBuffer.RowStride, metadata) { } @@ -110,8 +110,29 @@ public sealed class Image : Image int width, int height, ImageMetadata metadata) + : this(configuration, memoryGroup, width, height, width, metadata) + { + } + + /// + /// Initializes a new instance of the class + /// wrapping an external . + /// + /// The configuration providing initialization code which allows extending the library. + /// The memory source. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The number of elements between row starts. + /// The images metadata. + internal Image( + Configuration configuration, + MemoryGroup memoryGroup, + int width, + int height, + int rowStride, + ImageMetadata metadata) : base(configuration, TPixel.GetPixelTypeInfo(), metadata, width, height) - => this.frames = new ImageFrameCollection(this, width, height, memoryGroup); + => this.frames = new ImageFrameCollection(this, width, height, rowStride, memoryGroup); /// /// Initializes a new instance of the class @@ -287,16 +308,24 @@ public sealed class Image : Image } /// - /// Copy image pixels to . + /// Copy image pixels to using the root frame backing row layout. /// + /// + /// Destination length must be at least + /// ((Height - 1) * Frames.RootFrame.PixelBuffer.RowStride) + Width. + /// /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + public void CopyPixelDataTo(Span destination) => this.Frames.RootFrame.CopyPixelDataTo(destination); /// - /// Copy image pixels to . + /// Copy image pixels to using the root frame backing row layout. /// + /// + /// Destination length must be at least + /// (((Height - 1) * Frames.RootFrame.PixelBuffer.RowStride) + Width) * sizeof(TPixel) bytes. + /// /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + public void CopyPixelDataTo(Span destination) => this.Frames.RootFrame.CopyPixelDataTo(destination); /// /// Gets the representation of the pixels as a in the source image's pixel format @@ -311,17 +340,7 @@ public sealed class Image : Image /// The referencing the image buffer. /// The indicating the success. public bool DangerousTryGetSinglePixelMemory(out Memory memory) - { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) - { - memory = default; - return false; - } - - memory = mg.Single(); - return true; - } + => this.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out memory); /// /// Clones the current image. diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index f0fa1438d..96b9bc172 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -45,7 +45,7 @@ public static class Buffer2DExtensions Buffer2DRegion sourceRegion = source.GetRegion(rectangle); if (sourceRegion.IsFullBufferArea) { - sourceRegion.Buffer.FastMemoryGroup.CopyTo(buffer.FastMemoryGroup); + sourceRegion.Buffer.CopyTo(buffer); } else { @@ -81,7 +81,7 @@ public static class Buffer2DExtensions CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destinationIndex, columnCount); int elementSize = Unsafe.SizeOf(); - int width = buffer.Width * elementSize; + int rowByteStride = buffer.RowStride * elementSize; int sOffset = sourceIndex * elementSize; int dOffset = destinationIndex * elementSize; long count = columnCount * elementSize; @@ -98,7 +98,7 @@ public static class Buffer2DExtensions Buffer.MemoryCopy(sPtr, dPtr, count, count); - basePtr += width; + basePtr += rowByteStride; } } } diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index f4b257b58..7bfb6f573 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -59,9 +59,9 @@ public readonly struct Buffer2DRegion public int Height => this.Rectangle.Height; /// - /// Gets the pixel stride which is equal to the width of . + /// Gets the number of elements between row starts in . /// - public int Stride => this.Buffer.Width; + public int Stride => this.Buffer.RowStride; /// /// Gets the size of the area. @@ -146,9 +146,9 @@ public readonly struct Buffer2DRegion internal void Clear() { // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) + if (this.IsFullBufferArea && this.Buffer.RowStride == this.Buffer.Width) { - this.Buffer.FastMemoryGroup.Clear(); + this.Buffer.Clear(default); return; } @@ -166,9 +166,9 @@ public readonly struct Buffer2DRegion internal void Fill(T value) { // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) + if (this.IsFullBufferArea && this.Buffer.RowStride == this.Buffer.Width) { - this.Buffer.FastMemoryGroup.Fill(value); + this.Buffer.Clear(value); return; } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 39c6e62e1..3194aebae 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory; @@ -20,10 +21,27 @@ public sealed class Buffer2D : IDisposable /// The number of elements in a row. /// The number of rows. internal Buffer2D(MemoryGroup memoryGroup, int width, int height) + : this(memoryGroup, width, height, width) { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + /// The number of elements between row starts. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height, int rowStride) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride)); + this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; + this.RowStride = rowStride; } /// @@ -36,6 +54,11 @@ public sealed class Buffer2D : IDisposable /// public int Height { get; private set; } + /// + /// Gets the number of elements between row starts in the backing memory. + /// + public int RowStride { get; private set; } + /// /// Gets the backing . /// @@ -75,6 +98,168 @@ public sealed class Buffer2D : IDisposable } } + /// + /// Wraps an existing memory area as a with tightly packed rows. + /// + /// + /// This method does not transfer ownership of to the returned . + /// The caller is responsible for ensuring that the memory remains valid for the entire lifetime of the returned buffer. + /// If originates from an (for example from ), + /// do not dispose that owner while the returned buffer is still in use. + /// + /// The source memory. + /// The number of elements in each row. + /// The number of rows. + /// The wrapped instance. + /// Thrown when or is not positive. + /// Thrown when is shorter than width * height. +#pragma warning disable CA1000 // Do not declare static members on generic types + public static Buffer2D WrapMemory(Memory memory, int width, int height) +#pragma warning restore CA1000 // Do not declare static members on generic types + => WrapMemory(memory, width, height, width); + + /// + /// Wraps an existing memory area as a using the specified row stride. + /// + /// + /// This method does not transfer ownership of to the returned . + /// The caller is responsible for ensuring that the memory remains valid for the entire lifetime of the returned buffer. + /// If originates from an (for example from ), + /// do not dispose that owner while the returned buffer is still in use. + /// The minimum required length is ((height - 1) * stride) + width elements. + /// + /// The source memory. + /// The number of elements in each row. + /// The number of rows. + /// The number of elements between row starts in the source memory. + /// The wrapped instance. + /// + /// Thrown when or is not positive, + /// or when is less than . + /// + /// Thrown when is shorter than the required buffer size. +#pragma warning disable CA1000 // Do not declare static members on generic types + public static Buffer2D WrapMemory(Memory memory, int width, int height, int stride) +#pragma warning restore CA1000 // Do not declare static members on generic types + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(stride, width, nameof(stride)); + + long requiredLength = checked(((long)(height - 1) * stride) + width); + Guard.IsTrue(memory.Length >= requiredLength, nameof(memory), "The length of the input memory is less than the specified buffer size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(memory); + return new Buffer2D(memorySource, width, height, stride); + } + + /// + /// Gets the representation of the values as a single contiguous + /// when the backing group is a single tightly packed segment. + /// + /// The referencing the buffer. + /// + /// when the buffer can be copied as one contiguous block + /// without per-row handling; otherwise . + /// + public bool DangerousTryGetSingleMemory(out Memory memory) + { + if (this.MemoryGroup.Count > 1 || this.RowStride != this.Width) + { + memory = default; + return false; + } + + int logicalLength = checked((int)((long)this.Width * this.Height)); + memory = this.MemoryGroup[0][..logicalLength]; + return true; + } + + /// + /// Copies this buffer into using the source logical row layout. + /// + /// + /// When dimensions are equal, destination stride is respected. + /// When dimensions differ, source stride is used to copy the source logical layout into destination memory. + /// + /// The destination buffer. + internal void CopyTo(Buffer2D destination) + { + Guard.NotNull(destination, nameof(destination)); + + bool sameDimensions = this.Width == destination.Width && this.Height == destination.Height; + int destinationStride = sameDimensions ? destination.RowStride : this.RowStride; + + // Different dimensions use source logical layout. This supports SwapOrCopyContent, + // where metadata is swapped after data copy. + this.FastMemoryGroup.CopyTo( + this.RowStride, + destination.FastMemoryGroup, + destinationStride, + this.Width, + this.Height); + } + + /// + /// Copies this buffer into using the source row stride as destination layout. + /// + /// The destination span. + internal void CopyTo(Span destination) + { + long requiredLength = checked(((long)(this.Height - 1) * this.RowStride) + this.Width); + Guard.MustBeGreaterThanOrEqualTo(destination.Length, requiredLength, nameof(destination)); + + this.FastMemoryGroup.CopyTo( + this.RowStride, + destination, + this.RowStride, + this.Width, + this.Height); + } + + /// + /// Copies tightly packed row-major data from into this buffer. + /// + /// The source data. + internal void CopyFrom(ReadOnlySpan source) => this.CopyFrom(source, this.Width); + + /// + /// Copies row-major data from into this buffer using + /// elements between source row starts. + /// + /// The source data. + /// The number of elements between source row starts. + internal void CopyFrom(ReadOnlySpan source, int sourceStride) + { + Guard.MustBeGreaterThanOrEqualTo(sourceStride, this.Width, nameof(sourceStride)); + + long requiredLength = checked(((long)(this.Height - 1) * sourceStride) + this.Width); + Guard.MustBeGreaterThanOrEqualTo(source.Length, requiredLength, nameof(source)); + + // Copy row by row so padded source rows map correctly into the destination logical rows. + int sourceOffset = 0; + for (int y = 0; y < this.Height; y++) + { + source.Slice(sourceOffset, this.Width).CopyTo(this.DangerousGetRowSpan(y)); + sourceOffset += sourceStride; + } + } + + /// + /// Clears this buffer when is default; otherwise fills it with . + /// + /// The fill value. + internal void Clear(T value) + { + if (value.Equals(default)) + { + this.FastMemoryGroup.Clear(); + return; + } + + this.FastMemoryGroup.Fill(value); + } + /// /// Disposes the instance /// @@ -102,7 +287,13 @@ public sealed class Buffer2D : IDisposable this.ThrowYOutOfRangeException(y); } - return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + if (this.RowStride == this.Width) + { + return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + } + + int rowStart = checked(y * this.RowStride); + return this.FastMemoryGroup[0].Span.Slice(rowStart, this.Width); } internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) @@ -111,8 +302,10 @@ public sealed class Buffer2D : IDisposable DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); int stride = this.Width + padding; - - Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); + long rowStart = y * (long)this.RowStride; + Span slice = this.RowStride == this.Width + ? this.FastMemoryGroup.GetRemainingSliceOfBuffer(rowStart) + : this.FastMemoryGroup[0].Span[checked((int)rowStart)..]; if (slice.Length < stride) { @@ -127,7 +320,10 @@ public sealed class Buffer2D : IDisposable [MethodImpl(InliningOptions.ShortMethod)] internal ref T GetElementUnsafe(int x, int y) { - Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + Span span = this.RowStride == this.Width + ? this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width) + : this.FastMemoryGroup[0].Span.Slice(checked(y * this.RowStride), this.Width); + return ref span[x]; } @@ -141,6 +337,13 @@ public sealed class Buffer2D : IDisposable { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + + if (this.RowStride != this.Width) + { + int rowStart = checked(y * this.RowStride); + return this.FastMemoryGroup[0].Slice(rowStart, this.Width); + } + return this.FastMemoryGroup.View.GetBoundedMemorySlice(y * (long)this.Width, this.Width); } @@ -153,7 +356,7 @@ public sealed class Buffer2D : IDisposable /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; + internal Span DangerousGetSingleSpan() => this.FastMemoryGroup[0].Span; /// /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. @@ -164,7 +367,7 @@ public sealed class Buffer2D : IDisposable /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); + internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup[0]; /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), @@ -185,21 +388,31 @@ public sealed class Buffer2D : IDisposable } else { - if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.TotalLength) + long sourceLayoutLength = GetRequiredLength(source.Width, source.Height, source.RowStride); + long destinationLayoutLength = GetRequiredLength(destination.Width, destination.Height, destination.RowStride); + + bool destinationCanRepresentSource = destination.FastMemoryGroup.TotalLength >= sourceLayoutLength; + bool sourceCanRepresentDestination = source.FastMemoryGroup.TotalLength >= destinationLayoutLength; + if (!destinationCanRepresentSource || !sourceCanRepresentDestination) { throw new InvalidMemoryOperationException( "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); } - source.FastMemoryGroup.CopyTo(destination.MemoryGroup); + source.CopyTo(destination); } (destination.Width, source.Width) = (source.Width, destination.Width); (destination.Height, source.Height) = (source.Height, destination.Height); + (destination.RowStride, source.RowStride) = (source.RowStride, destination.RowStride); return swapped; } [MethodImpl(InliningOptions.ColdPath)] private void ThrowYOutOfRangeException(int y) => throw new ArgumentOutOfRangeException($"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); + + [MethodImpl(InliningOptions.ShortMethod)] + private static long GetRequiredLength(int width, int height, int stride) + => ((long)(height - 1) * stride) + width; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 148b5f6bf..b399d3d70 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -71,128 +71,159 @@ internal static class MemoryGroupExtensions return memory.Slice(bufferStart, length); } - internal static void CopyTo(this IMemoryGroup source, Span target) + /// + /// Copies a 2D logical region from into + /// using the provided source and target strides. + /// + /// The element type. + /// The source memory group. + /// Elements between source row starts. + /// The destination span. + /// Elements between destination row starts. + /// The logical row width to copy. + /// The number of rows to copy. + internal static void CopyTo( + this IMemoryGroup source, + int sourceStride, + Span target, + int targetStride, + int width, + int height) where T : struct { Guard.NotNull(source, nameof(source)); - Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); + Guard.MustBeGreaterThanOrEqualTo(width, 0, nameof(width)); + Guard.MustBeGreaterThanOrEqualTo(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(sourceStride, width, nameof(sourceStride)); + Guard.MustBeGreaterThanOrEqualTo(targetStride, width, nameof(targetStride)); - MemoryGroupCursor cur = new(source); - long position = 0; - while (position < source.TotalLength) - { - int fwd = Math.Min(cur.LookAhead(), target.Length); - cur.GetSpan(fwd).CopyTo(target); + long sourceRequired = height == 0 ? 0 : checked(((long)(height - 1) * sourceStride) + width); + long targetRequired = height == 0 ? 0 : checked(((long)(height - 1) * targetStride) + width); + Guard.MustBeGreaterThanOrEqualTo(source.TotalLength, sourceRequired, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.Length, targetRequired, nameof(target)); - cur.Forward(fwd); - target = target[fwd..]; - position += fwd; + if (width == 0 || height == 0) + { + return; } - } - internal static void CopyTo(this Span source, IMemoryGroup target) - where T : struct - => CopyTo((ReadOnlySpan)source, target); + MemoryGroupCursor sourceCursor = new(source); + int sourceSkip = sourceStride - width; - internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) - where T : struct - { - Guard.NotNull(target, nameof(target)); - Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); - - MemoryGroupCursor cur = new(target); - - while (!source.IsEmpty) + for (int y = 0; y < height; y++) { - int fwd = Math.Min(cur.LookAhead(), source.Length); - source[..fwd].CopyTo(cur.GetSpan(fwd)); - cur.Forward(fwd); - source = source[fwd..]; + int rowStart = checked(y * targetStride); + Span destinationRow = target.Slice(rowStart, width); + CopyFromCursorToSpan(ref sourceCursor, destinationRow); + + // Trailing padding after the last row is optional, so only skip between rows. + if (y < height - 1) + { + ForwardCursor(ref sourceCursor, sourceSkip); + } } } - internal static void CopyTo(this IMemoryGroup? source, IMemoryGroup? target) + /// + /// Copies a 2D logical region from into + /// using the provided source and target strides. + /// + /// The element type. + /// The source memory group. + /// Elements between source row starts. + /// The destination memory group. + /// Elements between destination row starts. + /// The logical row width to copy. + /// The number of rows to copy. + internal static void CopyTo( + this IMemoryGroup source, + int sourceStride, + IMemoryGroup target, + int targetStride, + int width, + int height) where T : struct { Guard.NotNull(source, nameof(source)); Guard.NotNull(target, nameof(target)); Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + Guard.MustBeGreaterThanOrEqualTo(width, 0, nameof(width)); + Guard.MustBeGreaterThanOrEqualTo(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(sourceStride, width, nameof(sourceStride)); + Guard.MustBeGreaterThanOrEqualTo(targetStride, width, nameof(targetStride)); - if (source.IsEmpty()) + long sourceRequired = height == 0 ? 0 : checked(((long)(height - 1) * sourceStride) + width); + long targetRequired = height == 0 ? 0 : checked(((long)(height - 1) * targetStride) + width); + Guard.MustBeGreaterThanOrEqualTo(source.TotalLength, sourceRequired, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, targetRequired, nameof(target)); + + if (width == 0 || height == 0) { return; } - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); + MemoryGroupCursor sourceCursor = new(source); + MemoryGroupCursor targetCursor = new(target); + int sourceSkip = sourceStride - width; + int targetSkip = targetStride - width; - while (position < source.TotalLength) + for (int y = 0; y < height; y++) { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - srcSpan.CopyTo(trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; + CopyFromCursorToCursor(ref sourceCursor, ref targetCursor, width); + + // Trailing padding after the last row is optional, so only skip between rows. + if (y < height - 1) + { + ForwardCursor(ref sourceCursor, sourceSkip); + ForwardCursor(ref targetCursor, targetSkip); + } } } - internal static void TransformTo( - this IMemoryGroup source, - IMemoryGroup target, - TransformItemsDelegate transform) - where TSource : struct - where TTarget : struct + private static void CopyFromCursorToCursor( + ref MemoryGroupCursor source, + ref MemoryGroupCursor target, + int count) + where T : struct { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(target, nameof(target)); - Guard.NotNull(transform, nameof(transform)); - Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); - Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); - - if (source.IsEmpty()) + int remaining = count; + while (remaining > 0) { - return; + int fwd = Math.Min(remaining, Math.Min(source.LookAhead(), target.LookAhead())); + source.GetSpan(fwd).CopyTo(target.GetSpan(fwd)); + source.Forward(fwd); + target.Forward(fwd); + remaining -= fwd; } + } - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); - - while (position < source.TotalLength) + private static void CopyFromCursorToSpan(ref MemoryGroupCursor source, Span target) + where T : struct + { + int remaining = target.Length; + while (remaining > 0) { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - transform(srcSpan, trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; + int copied = target.Length - remaining; + int fwd = Math.Min(remaining, source.LookAhead()); + source.GetSpan(fwd).CopyTo(target[copied..]); + source.Forward(fwd); + remaining -= fwd; } } - internal static void TransformInplace( - this IMemoryGroup memoryGroup, - TransformItemsInplaceDelegate transform) + private static void ForwardCursor(ref MemoryGroupCursor cursor, int steps) where T : struct { - foreach (Memory memory in memoryGroup) + int remaining = steps; + while (remaining > 0) { - transform(memory.Span); + int fwd = Math.Min(remaining, cursor.LookAhead()); + cursor.Forward(fwd); + remaining -= fwd; } } - internal static bool IsEmpty(this IMemoryGroup group) - where T : struct - => group.Count == 0; - private struct MemoryGroupCursor where T : struct { diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index ff306e1e4..57ebcab76 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -29,6 +29,9 @@ public static class MemoryAllocatorExtensions AllocationOptions options = AllocationOptions.None) where T : struct { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + long groupLength = (long)width * height; MemoryGroup memoryGroup; if (preferContiguosImageBuffers && groupLength < int.MaxValue) @@ -104,6 +107,9 @@ public static class MemoryAllocatorExtensions AllocationOptions options = AllocationOptions.None) where T : struct { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + long groupLength = (long)width * height; MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( groupLength, diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs deleted file mode 100644 index bc3d17f8f..000000000 --- a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -#pragma warning disable SA1649 // File name should match first type name -internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs deleted file mode 100644 index d1ef51fb8..000000000 --- a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -internal delegate void TransformItemsInplaceDelegate(Span data); diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 96c350c6c..5ef2422a3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -53,7 +53,7 @@ internal class CropProcessor : TransformProcessor && this.SourceRectangle == this.cropRectangle) { // the cloned will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + source.PixelBuffer.CopyTo(destination.PixelBuffer); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index 8d3ded09f..e9d0ecf57 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -97,7 +97,7 @@ internal class RotateProcessor : AffineTransformProcessor if (MathF.Abs(degrees) < Constants.Epsilon) { // The destination will be blank here so copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + source.PixelBuffer.CopyTo(destination.PixelBuffer); return true; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 49439545b..48f4a746f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -91,7 +91,7 @@ internal class ResizeProcessor : TransformProcessor, IResampling ImageFrame destinationFrame = destination.Frames[i]; // The cloned will be blank here copy all the pixel data over - sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + sourceFrame.PixelBuffer.CopyTo(destinationFrame.PixelBuffer); } return; diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index d7f2c7d28..417ee18fb 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -64,7 +64,7 @@ public abstract partial class ImageFrameCollectionTests using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); }); - Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); + Assert.StartsWith($"Parameter \"data\" ({typeof(long)}) must be greater than or equal to {100}, was {0}", ex.Message); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 47ae0daf7..171aa6d4c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -52,5 +52,79 @@ public partial class ImageTests Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); } + + [Fact] + public void FromPixels_WithRowStride() + { + Rgba32[] data = + [ + Color.Black.ToPixel(), + Color.White.ToPixel(), + Color.Red.ToPixel(), // padding + Color.White.ToPixel(), + Color.Black.ToPixel(), + Color.Red.ToPixel() // padding + ]; + + using Image img = Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 3); + Assert.NotNull(img); + Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); + Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); + Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); + Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes() + { + byte[] data = + [ + 0, 0, 0, 255, // 0,0 + 255, 255, 255, 255, // 0,1 + 255, 0, 0, 255, // padding + 255, 255, 255, 255, // 1,0 + 0, 0, 0, 255, // 1,1 + 255, 0, 0, 255 // padding + ]; + + using Image img = Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 12); + Assert.NotNull(img); + Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); + Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); + Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); + Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); + } + + [Fact] + public void FromPixels_WithRowStride_InvalidStride_Throws() + { + Rgba32[] data = new Rgba32[6]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 1)); + } + + [Fact] + public void FromPixels_WithRowStride_InvalidLength_Throws() + { + Rgba32[] data = new Rgba32[4]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 3)); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes_InvalidStride_Throws() + { + byte[] data = new byte[24]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 10)); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes_InvalidLength_Throws() + { + byte[] data = new byte[19]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 12)); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 6322e65aa..ef98fc43a 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -141,6 +141,103 @@ public partial class ImageTests } } + [Fact] + public void WrapMemory_MemoryOfT_Strided_CreatedImageIsCorrect() + { + Rgba32[] source = + [ + new Rgba32(1, 1, 1, 255), + new Rgba32(2, 2, 2, 255), + new Rgba32(3, 3, 3, 255), + new Rgba32(90, 90, 90, 255), + new Rgba32(4, 4, 4, 255), + new Rgba32(5, 5, 5, 255), + new Rgba32(6, 6, 6, 255), + new Rgba32(91, 91, 91, 255) + ]; + + using Image image = Image.WrapMemory(source.AsMemory(), width: 3, height: 2, rowStride: 4); + + Assert.Equal(4, image.Frames.RootFrame.PixelBuffer.RowStride); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory _)); + Assert.Equal(source[0], image[0, 0]); + Assert.Equal(source[2], image[2, 0]); + Assert.Equal(source[4], image[0, 1]); + Assert.Equal(source[6], image[2, 1]); + } + + [Fact] + public void WrapMemory_MemoryOfT_Strided_CopyPixelDataTo_UsesRowStrideLayout() + { + Rgba32[] source = + [ + new Rgba32(1, 1, 1, 255), + new Rgba32(2, 2, 2, 255), + new Rgba32(3, 3, 3, 255), + new Rgba32(90, 90, 90, 255), + new Rgba32(4, 4, 4, 255), + new Rgba32(5, 5, 5, 255), + new Rgba32(6, 6, 6, 255), + new Rgba32(91, 91, 91, 255) + ]; + + using Image image = Image.WrapMemory(source.AsMemory(), width: 3, height: 2, rowStride: 4); + + Rgba32 sentinel = new(250, 1, 1, 255); + Rgba32[] destination = [sentinel, sentinel, sentinel, sentinel, sentinel, sentinel, sentinel]; + image.CopyPixelDataTo(destination); + + Assert.Equal(source[0], destination[0]); + Assert.Equal(source[1], destination[1]); + Assert.Equal(source[2], destination[2]); + Assert.Equal(sentinel, destination[3]); + Assert.Equal(source[4], destination[4]); + Assert.Equal(source[5], destination[5]); + Assert.Equal(source[6], destination[6]); + Assert.ThrowsAny(() => image.CopyPixelDataTo(new Rgba32[6])); + } + + [Fact] + public void WrapMemory_MemoryOfByte_Strided_CreatedImageIsCorrect() + { + int pixelSize = Unsafe.SizeOf(); + byte[] sourceBytes = new byte[8 * pixelSize]; + Span source = MemoryMarshal.Cast(sourceBytes); + + source[0] = new Rgba32(1, 1, 1, 255); + source[1] = new Rgba32(2, 2, 2, 255); + source[2] = new Rgba32(3, 3, 3, 255); + source[4] = new Rgba32(4, 4, 4, 255); + source[5] = new Rgba32(5, 5, 5, 255); + source[6] = new Rgba32(6, 6, 6, 255); + + using Image image = Image.WrapMemory( + sourceBytes.AsMemory(), + width: 3, + height: 2, + rowStrideInBytes: 4 * pixelSize); + + Assert.Equal(4, image.Frames.RootFrame.PixelBuffer.RowStride); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory _)); + Assert.Equal(source[0], image[0, 0]); + Assert.Equal(source[2], image[2, 0]); + Assert.Equal(source[4], image[0, 1]); + Assert.Equal(source[6], image[2, 1]); + } + + [Fact] + public void WrapMemory_Strided_InvalidStride_Throws() + { + Rgba32[] pixelSource = new Rgba32[8]; + byte[] byteSource = new byte[8 * Unsafe.SizeOf()]; + + Assert.ThrowsAny(() => Image.WrapMemory(pixelSource.AsMemory(), width: 3, height: 2, rowStride: 2)); + Assert.ThrowsAny(() => Image.WrapMemory(pixelSource.AsMemory(0, 6), width: 3, height: 2, rowStride: 4)); + + Assert.ThrowsAny(() => Image.WrapMemory(byteSource.AsMemory(), width: 3, height: 2, rowStrideInBytes: (4 * Unsafe.SizeOf()) - 1)); + Assert.ThrowsAny(() => Image.WrapMemory(byteSource.AsMemory(0, 6 * Unsafe.SizeOf()), width: 3, height: 2, rowStrideInBytes: 4 * Unsafe.SizeOf())); + } + [Fact] public void WrapSystemDrawingBitmap_WhenObserved() { diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs new file mode 100644 index 000000000..b19095be9 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests +{ + [Fact] + public void CopyToSpan_StridedSource_WritesDestinationUsingSourceStride() + { + int[] source = [1, 2, 3, 777, 4, 5, 6, 888]; + int[] destination = [-1, -1, -1, -1, -1, -1, -1]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2, stride: 4); + buffer.CopyTo(destination); + + Assert.Equal(new[] { 1, 2, 3, -1, 4, 5, 6 }, destination); + } + + [Fact] + public void CopyToSpan_PackedSource_WritesPackedDestination() + { + int[] source = [1, 2, 3, 4, 5, 6]; + int[] destination = new int[6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2); + buffer.CopyTo(destination); + + Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, destination); + } + + [Fact] + public void CopyToSpan_StridedSource_ThrowsWhenDestinationTooShort() + { + int[] source = [1, 2, 3, 777, 4, 5, 6, 888]; + int[] destination = new int[6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2, stride: 4); + Assert.Throws(() => buffer.CopyTo(destination)); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs index e17c8c46f..caf935cc4 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs @@ -152,5 +152,74 @@ public partial class Buffer2DTests Assert.Equal(color, source.MemoryGroup[0].Span[10]); Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeSameTotal_PackedLayout_ShouldCopy() + { + int[] data = new int[6]; + using TestMemoryManager destOwner = new(data); + using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 2, 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(3, 2, AllocationOptions.Clean); + + source[0, 0] = 1; + source[1, 0] = 2; + source[2, 0] = 3; + source[0, 1] = 4; + source[1, 1] = 5; + source[2, 1] = 6; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(3, 2), dest.Size()); + Assert.Equal(6, dest[2, 1]); + } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeSameTotal_StridedLayout_ShouldCopy() + { + int[] data = new int[5]; + using Buffer2D dest = Buffer2D.WrapMemory(data.AsMemory(), width: 2, height: 2, stride: 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(1, 5, AllocationOptions.Clean); + + source[0, 0] = 1; + source[0, 1] = 2; + source[0, 2] = 3; + source[0, 3] = 4; + source[0, 4] = 5; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(1, 5), dest.Size()); + Assert.Equal(1, dest[0, 0]); + Assert.Equal(2, dest[0, 1]); + Assert.Equal(3, dest[0, 2]); + Assert.Equal(4, dest[0, 3]); + Assert.Equal(5, dest[0, 4]); + } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeDifferentTotal_ButBothLayoutsFit_ShouldCopy() + { + int[] data = new int[5]; + using TestMemoryManager destOwner = new(data); + using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 1, 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(2, 2, AllocationOptions.Clean); + + source[0, 0] = 1; + source[1, 0] = 2; + source[0, 1] = 3; + source[1, 1] = 4; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(2, 2), dest.Size()); + Assert.Equal(1, dest[0, 0]); + Assert.Equal(2, dest[1, 0]); + Assert.Equal(3, dest[0, 1]); + Assert.Equal(4, dest[1, 1]); + } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs new file mode 100644 index 000000000..f65f193ea --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests +{ + [Fact] + public void WrapMemory_Packed_DangerousTryGetSinglePixelMemory_ReturnsTrue() + { + int[] data = [1, 2, 3, 4, 5, 6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 3); + + Assert.True(buffer.DangerousTryGetSingleMemory(out Memory memory)); + Assert.Equal(3, buffer.RowStride); + Assert.Equal(6, memory.Length); + Assert.True(Unsafe.AreSame(ref data[0], ref memory.Span[0])); + Assert.SpanPointsTo(buffer.DangerousGetRowSpan(1), data.AsMemory(), bufferOffset: 3); + } + + [Fact] + public void WrapMemory_Strided_DangerousTryGetSinglePixelMemory_ReturnsFalse() + { + int[] data = [1, 2, 3, 777, 4, 5, 6, 888]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 4); + + Assert.False(buffer.DangerousTryGetSingleMemory(out Memory _)); + Assert.Equal(4, buffer.RowStride); + Assert.Equal(4, new Buffer2DRegion(buffer).Stride); + + Span row0 = buffer.DangerousGetRowSpan(0); + Span row1 = buffer.DangerousGetRowSpan(1); + + Assert.Equal(3, row0.Length); + Assert.Equal(3, row1.Length); + Assert.Equal(1, row0[0]); + Assert.Equal(3, row0[2]); + Assert.Equal(4, row1[0]); + Assert.Equal(6, row1[2]); + + Assert.SpanPointsTo(row0, data.AsMemory(), bufferOffset: 0); + Assert.SpanPointsTo(row1, data.AsMemory(), bufferOffset: 4); + } + + [Fact] + public void WrapMemory_Packed_WithTrailingData_DangerousTryGetSinglePixelMemory_IsLogicalSize() + { + int[] data = [1, 2, 3, 4, 5, 6, 777, 888]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 3); + + Assert.True(buffer.DangerousTryGetSingleMemory(out Memory memory)); + Assert.Equal(6, memory.Length); + Assert.True(Unsafe.AreSame(ref data[0], ref memory.Span[0])); + } + + [Fact] + public void WrapMemory_Strided_ThrowsWhenStrideIsLessThanWidth() + { + int[] data = new int[10]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 2)); + } + + [Fact] + public void WrapMemory_Strided_ThrowsWhenInputMemoryTooSmall() + { + int[] data = new int[6]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 4)); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(0, 0)] + [InlineData(-1, 1)] + [InlineData(1, -1)] + public void WrapMemory_ThrowsWhenWidthOrHeightIsNotPositive(int width, int height) + { + int[] data = new int[16]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width, height)); + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width, height, stride: 4)); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 6dfdd294c..8e3f3d286 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -50,17 +50,11 @@ public partial class Buffer2DTests [InlineData(Big, 1, 0)] [InlineData(60, 42, 0)] [InlineData(3, 0, 0)] - public unsafe void Construct_Empty(int bufferCapacity, int width, int height) + public unsafe void Construct_Empty_Throws(int bufferCapacity, int width, int height) { this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); - } + Assert.Throws(() => this.MemoryAllocator.Allocate2D(width, height)); } [Theory] @@ -339,25 +333,47 @@ public partial class Buffer2DTests Assert.NotSame(mgBefore, buffer1.MemoryGroup); } - public static TheoryData InvalidLengths { get; set; } = new() + public static TheoryData InvalidDimensions { get; set; } = new() { { new Size(-1, -1) }, + { new Size(0, 1) }, + { new Size(1, 0) }, + { new Size(0, 0) } + }; + + public static TheoryData OverflowDimensions { get; set; } = new() + { { new Size(32768, 32769) }, { new Size(32769, 32768) } }; [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(Size size) + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2D(size.Width, size.Height)); + + [Theory] + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_SizeOverload_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2D(new Size(size))); + + [Theory] + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_OverAligned_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2DOveraligned(size.Width, size.Height, 1)); + + [Theory] + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2D(size.Width, size.Height)); [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_Size(Size size) + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_SizeOverload_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2D(new Size(size))); [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_OverAligned(Size size) + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_OverAligned_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2DOveraligned(size.Width, size.Height, 1)); } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs index c140571fc..ee5cd9cd0 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -9,100 +9,74 @@ public partial class MemoryGroupTests { public class CopyTo : MemoryGroupTestsBase { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + [Fact] + public void GroupToSpan_StridedSource_DoesNotRequireTrailingPadding() { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.CopyTo(trg); + using MemoryGroup src = this.CreateTestGroup(totalLength: 7, bufferLength: 4, fillSequence: true); + int[] trg = new int[6]; - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); + src.CopyTo( + sourceStride: 4, + trg, + targetStride: 3, + width: 3, + height: 2); - Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } + Assert.Equal(new[] { 1, 2, 3, 5, 6, 7 }, trg); } [Fact] - public void WhenTargetBufferTooShort_Throws() + public void GroupToGroup_StridedCopy_DoesNotRequireTrailingPadding() { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.CopyTo(trg)); + using MemoryGroup src = this.CreateTestGroup(totalLength: 11, bufferLength: 5, fillSequence: true); + using MemoryGroup trg = this.CreateTestGroup(totalLength: 13, bufferLength: 6, fillSequence: false); + + src.CopyTo( + sourceStride: 4, + trg, + targetStride: 5, + width: 3, + height: 3); + + Assert.Equal(1, GetElementAtLinearIndex(trg, 0)); + Assert.Equal(2, GetElementAtLinearIndex(trg, 1)); + Assert.Equal(3, GetElementAtLinearIndex(trg, 2)); + Assert.Equal(5, GetElementAtLinearIndex(trg, 5)); + Assert.Equal(6, GetElementAtLinearIndex(trg, 6)); + Assert.Equal(7, GetElementAtLinearIndex(trg, 7)); + Assert.Equal(9, GetElementAtLinearIndex(trg, 10)); + Assert.Equal(10, GetElementAtLinearIndex(trg, 11)); + Assert.Equal(11, GetElementAtLinearIndex(trg, 12)); } - [Theory] - [InlineData(30, 10, 40)] - [InlineData(42, 23, 42)] - [InlineData(1, 3, 10)] - [InlineData(0, 4, 0)] - public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) + [Fact] + public void GroupToSpan_StridedSource_HeightOne() { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - src.CopyTo(trg); + using MemoryGroup src = this.CreateTestGroup(totalLength: 3, bufferLength: 2, fillSequence: true); + int[] trg = new int[3]; - int expected = 1; - foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) - { - Assert.Equal(expected, val); - expected++; - } - } + src.CopyTo( + sourceStride: 8, + trg, + targetStride: 3, + width: 3, + height: 1); - [Theory] - [InlineData(20, 7, 19)] - [InlineData(2, 1, 1)] - public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - Assert.ThrowsAny(() => src.CopyTo(trg)); + Assert.Equal(new[] { 1, 2, 3 }, trg); } - [Theory] - [InlineData(30, 35, 10)] - [InlineData(42, 23, 42)] - [InlineData(10, 3, 1)] - [InlineData(0, 3, 0)] - public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) + private static int GetElementAtLinearIndex(MemoryGroup group, int index) { - int[] src = new int[spanLength]; - for (int i = 0; i < src.Length; i++) - { - src[i] = i + 1; - } - - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); - src.AsSpan().CopyTo(trg); - - int position = 0; - for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) + int pos = 0; + for (MemoryGroupIndex i = group.MinIndex(); i < group.MaxIndex(); i += 1, pos++) { - int expected = position + 1; - Assert.Equal(expected, trg.GetElementAt(i)); + if (pos == index) + { + return group.GetElementAt(i); + } } - } - [Theory] - [InlineData(10, 3, 11)] - [InlineData(0, 3, 1)] - public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - int[] src = new int[spanLength]; - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index fe1867f20..b82b20120 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -26,76 +26,6 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase Assert.False(g.IsValid); } -#pragma warning disable SA1509 - private static readonly TheoryData CopyAndTransformData = - new() - { - { 20, 10, 20, 10 }, - { 20, 5, 20, 4 }, - { 20, 4, 20, 5 }, - { 18, 6, 20, 5 }, - { 19, 10, 20, 10 }, - { 21, 10, 22, 2 }, - { 1, 5, 5, 4 }, - - { 30, 12, 40, 5 }, - { 30, 5, 40, 12 }, - }; - - public class TransformTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.TransformTo(trg, MultiplyAllBy2); - - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); - - Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } - - [Fact] - public void WhenTargetBufferTooShort_Throws() - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); - } - } - - [Theory] - [InlineData(100, 5)] - [InlineData(100, 101)] - public void TransformInplace(int totalLength, int bufferLength) - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - - src.TransformInplace(s => MultiplyAllBy2(s, s)); - - int cnt = 1; - for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) - { - int val = src.GetElementAt(i); - Assert.Equal(expected: cnt * 2, val); - cnt++; - } - } - [Fact] public void Wrap() { @@ -224,12 +154,4 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase } } - private static void MultiplyAllBy2(ReadOnlySpan source, Span target) - { - Assert.Equal(source.Length, target.Length); - for (int k = 0; k < source.Length; k++) - { - target[k] = source[k] * 2; - } - } } From 6a4d10291ba6bf6f98a16e30ee1e7b78862fc0dd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Mar 2026 18:12:04 +1000 Subject: [PATCH 027/240] Update ImageTests.WrapMemory.cs --- tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index ef98fc43a..65eeb124f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -202,7 +202,7 @@ public partial class ImageTests { int pixelSize = Unsafe.SizeOf(); byte[] sourceBytes = new byte[8 * pixelSize]; - Span source = MemoryMarshal.Cast(sourceBytes); + Span source = MemoryMarshal.Cast(sourceBytes.AsSpan()); source[0] = new Rgba32(1, 1, 1, 255); source[1] = new Rgba32(2, 2, 2, 255); From d557bb29e0a4d4dd022d07dbd1caff4791b91b23 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Mar 2026 20:17:24 +1000 Subject: [PATCH 028/240] Fix review feedback --- src/ImageSharp/Image.WrapMemory.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index e44af5d0d..eac202a41 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -609,14 +609,14 @@ public abstract partial class Image ImageMetadata metadata) where TPixel : unmanaged, IPixel { - Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null"); + Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must not be null"); Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(metadata, nameof(metadata)); int rowStride = GetPixelRowStrideFromByteStride(width, rowStrideInBytes, nameof(rowStrideInBytes)); long requiredLength = GetRequiredLength(width, height, rowStride); - Guard.MustBeLessThanOrEqualTo(requiredLength, int.MaxValue, nameof(height)); + Guard.MustBeLessThanOrEqualTo(requiredLength, int.MaxValue, nameof(requiredLength)); Guard.MustBeGreaterThanOrEqualTo(bufferSizeInBytes / Unsafe.SizeOf(), requiredLength, nameof(bufferSizeInBytes)); UnmanagedMemoryManager memoryManager = new(pointer, (int)requiredLength); diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 3194aebae..fe997a4fb 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -356,7 +356,7 @@ public sealed class Buffer2D : IDisposable /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span DangerousGetSingleSpan() => this.FastMemoryGroup[0].Span; + internal Span DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; /// /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. @@ -367,7 +367,7 @@ public sealed class Buffer2D : IDisposable /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup[0]; + internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), @@ -414,5 +414,5 @@ public sealed class Buffer2D : IDisposable [MethodImpl(InliningOptions.ShortMethod)] private static long GetRequiredLength(int width, int height, int stride) - => ((long)(height - 1) * stride) + width; + => checked(((long)(height - 1) * stride) + width); } From 38fcdbf849c0166b0215504142c90705a0f82764 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:50:48 +0000 Subject: [PATCH 029/240] Bump actions/setup-dotnet from 4 to 5 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cc5708adb..46f659c45 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -166,14 +166,14 @@ jobs: - name: DotNet Setup if: ${{ matrix.options.sdk-preview != true }} - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x - name: DotNet Setup Preview if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 10.0.x diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 63fd0cbbb..a095d1abe 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -67,7 +67,7 @@ jobs: restore-keys: ${{ runner.os }}-nuget- - name: DotNet Setup - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x From 8f7dc2c166010d5f03c8deb2a060d32da43631ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:35:51 +0000 Subject: [PATCH 030/240] Bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 6 +++--- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 46f659c45..ab6f65366 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,7 +27,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive @@ -137,7 +137,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive @@ -227,7 +227,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index a095d1abe..ae7302bdc 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -31,7 +31,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive From 34e7f9700d8e68bf00c27cffa678b0dfcf539365 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 14:44:03 +1000 Subject: [PATCH 031/240] Fix #3067 --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 19 ++++++++++----- .../Formats/Bmp/BmpDecoderTests.cs | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 17c1f545b..db53c6946 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -130,6 +130,13 @@ internal sealed class BmpDecoderCore : ImageDecoderCore Image? image = null; try { + ushort bitsPerPixel = this.infoHeader.BitsPerPixel; + + if (bitsPerPixel is not (1 or 2 or 4 or 8 or 16 or 24 or 32)) + { + BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); + } + int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); @@ -138,23 +145,23 @@ internal sealed class BmpDecoderCore : ImageDecoderCore switch (this.infoHeader.Compression) { - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: + case BmpCompression.RGB when bitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32: + case BmpCompression.RGB when bitsPerPixel is 32: this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24: + case BmpCompression.RGB when bitsPerPixel is 24: this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16: + case BmpCompression.RGB when bitsPerPixel is 16: this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask: + case BmpCompression.RGB when bitsPerPixel is <= 8 && this.processedAlphaMask: this.ReadRgbPaletteWithAlphaMask( stream, pixels, @@ -166,7 +173,7 @@ internal sealed class BmpDecoderCore : ImageDecoderCore inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8: + case BmpCompression.RGB when bitsPerPixel is <= 8: this.ReadRgbPalette( stream, pixels, diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 94cfe85ee..9487c4ce9 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -571,4 +571,27 @@ public class BmpDecoderTests }); Assert.IsType(ex.InnerException); } + + [Fact] + public void BmpDecoder_ThrowsException_Issue3067() + { + // Construct minimal BMP with bitsPerPixel = 0 + byte[] bmp = new byte[54]; + bmp[0] = (byte)'B'; + bmp[1] = (byte)'M'; + BitConverter.GetBytes(54).CopyTo(bmp, 2); + BitConverter.GetBytes(54).CopyTo(bmp, 10); + BitConverter.GetBytes(40).CopyTo(bmp, 14); + BitConverter.GetBytes(1).CopyTo(bmp, 18); + BitConverter.GetBytes(1).CopyTo(bmp, 22); + BitConverter.GetBytes((short)1).CopyTo(bmp, 26); + BitConverter.GetBytes((short)0).CopyTo(bmp, 28); // bitsPerPixel = 0 + + using MemoryStream stream = new(bmp); + + InvalidImageContentException ex = Assert.Throws(() => + { + using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); + }); + } } From 07d2c04bf19b4698b6aca9b9bb4d4a33ba0f7622 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 14:51:01 +1000 Subject: [PATCH 032/240] Potential fix for pull request finding 'Useless assignment to local variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 9487c4ce9..6ebe1bf4e 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -589,7 +589,7 @@ public class BmpDecoderTests using MemoryStream stream = new(bmp); - InvalidImageContentException ex = Assert.Throws(() => + Assert.Throws(() => { using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); }); From 1d87e61a96640140d45eb8a1c3deb9af654d4363 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 15:05:47 +1000 Subject: [PATCH 033/240] Update BmpDecoderCore.cs --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index db53c6946..a1de790c3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -130,14 +130,8 @@ internal sealed class BmpDecoderCore : ImageDecoderCore Image? image = null; try { - ushort bitsPerPixel = this.infoHeader.BitsPerPixel; - - if (bitsPerPixel is not (1 or 2 or 4 or 8 or 16 or 24 or 32)) - { - BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); - } - int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); + ushort bitsPerPixel = this.infoHeader.BitsPerPixel; image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); @@ -149,19 +143,23 @@ internal sealed class BmpDecoderCore : ImageDecoderCore this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 32: this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 24: this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 16: this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when bitsPerPixel is <= 8 && this.processedAlphaMask: + + case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8 && this.processedAlphaMask: this.ReadRgbPaletteWithAlphaMask( stream, pixels, @@ -173,7 +171,8 @@ internal sealed class BmpDecoderCore : ImageDecoderCore inverted); break; - case BmpCompression.RGB when bitsPerPixel is <= 8: + + case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8: this.ReadRgbPalette( stream, pixels, @@ -186,6 +185,10 @@ internal sealed class BmpDecoderCore : ImageDecoderCore break; + case BmpCompression.RGB when bitsPerPixel is <= 0 or > 32: + BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); + break; + case BmpCompression.RLE24: this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); From 128d6f943a8e6b190352db53674e3ef87fb15d36 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 15:31:20 +1000 Subject: [PATCH 034/240] Update IcoDecoderTests.cs --- .../Formats/Icon/Ico/IcoDecoderTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 85ff51b18..539826799 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -204,12 +204,19 @@ public class IcoDecoderTests } [Theory] - [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidBpp, PixelTypes.Rgba32)] + public void InvalidThrows_InvalidImageContentException(TestImageProvider provider) + => Assert.Throws(() => + { + using Image image = provider.GetImage(IcoDecoder.Instance); + }); + + [Theory] + [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidCompression, PixelTypes.Rgba32)] [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] - public void InvalidTest(TestImageProvider provider) + public void InvalidThows_NotSupportedException(TestImageProvider provider) => Assert.Throws(() => { using Image image = provider.GetImage(IcoDecoder.Instance); From ae9ca4c5bf56f1dec79c9fe96461fbfd5d5434b2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 22:30:35 +1000 Subject: [PATCH 035/240] Fix #3064 R --- .../LutABCalculator.CalculationType.cs | 17 ++- .../Icc/Calculators/LutABCalculator.cs | 141 ++++++++++-------- .../Icc/IccConverterbase.Conversions.cs | 2 +- .../DataReader/IccDataReader.TagDataEntry.cs | 110 +++++--------- .../Metadata/Profiles/ICC/IccProfile.cs | 6 +- .../TagDataEntries/IccLutAToBTagDataEntry.cs | 121 ++++++++------- .../TagDataEntries/IccLutBToATagDataEntry.cs | 111 ++++++++------ .../Formats/Jpg/JpegDecoderTests.cs | 15 ++ tests/ImageSharp.Tests/TestImages.cs | 1 + ...B_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png | 3 + .../Input/Jpg/icc-profiles/issue-3064.jpg | 3 + 11 files changed, 288 insertions(+), 242 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png create mode 100644 tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs index 253239cb7..60a7e50b9 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs @@ -5,14 +5,19 @@ namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal partial class LutABCalculator { + /// + /// Identifies the transform direction for the configured LUT calculator. + /// private enum CalculationType { - AtoB = 1 << 3, - BtoA = 1 << 4, + /// + /// Converts from device space to PCS using ICC mAB stage order. + /// + AtoB, - SingleCurve = 1, - CurveMatrix = 2, - CurveClut = 3, - Full = 4, + /// + /// Converts from PCS to device space using ICC mBA stage order. + /// + BtoA, } } diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs index 172d80639..83e51206a 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs @@ -17,67 +17,106 @@ internal partial class LutABCalculator : IVector4Calculator private MatrixCalculator matrixCalculator; private ClutCalculator clutCalculator; + /// + /// Initializes a new instance of the class for an ICC mAB transform. + /// + /// The parsed A-to-B LUT entry. public LutABCalculator(IccLutAToBTagDataEntry entry) { Guard.NotNull(entry, nameof(entry)); this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); - this.type |= CalculationType.AtoB; + this.type = CalculationType.AtoB; } + /// + /// Initializes a new instance of the class for an ICC mBA transform. + /// + /// The parsed B-to-A LUT entry. public LutABCalculator(IccLutBToATagDataEntry entry) { Guard.NotNull(entry, nameof(entry)); this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); - this.type |= CalculationType.BtoA; + this.type = CalculationType.BtoA; } + /// + /// Calculates the transformed value by applying the configured ICC LUT stages in specification order. + /// + /// The input value. + /// The transformed value. public Vector4 Calculate(Vector4 value) { switch (this.type) { - case CalculationType.Full | CalculationType.AtoB: - value = this.curveACalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - value = this.curveMCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.Full | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - value = this.curveMCalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveACalculator.Calculate(value); - - case CalculationType.CurveClut | CalculationType.AtoB: - value = this.curveACalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.CurveClut | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.clutCalculator.Calculate(value); - return this.curveACalculator.Calculate(value); - - case CalculationType.CurveMatrix | CalculationType.AtoB: - value = this.curveMCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveBCalculator.Calculate(value); - - case CalculationType.CurveMatrix | CalculationType.BtoA: - value = this.curveBCalculator.Calculate(value); - value = this.matrixCalculator.Calculate(value); - return this.curveMCalculator.Calculate(value); - - case CalculationType.SingleCurve | CalculationType.AtoB: - case CalculationType.SingleCurve | CalculationType.BtoA: - return this.curveBCalculator.Calculate(value); + case CalculationType.AtoB: + // ICC mAB order: A, CLUT, M, Matrix, B. + if (this.curveACalculator != null) + { + value = this.curveACalculator.Calculate(value); + } + + if (this.clutCalculator != null) + { + value = this.clutCalculator.Calculate(value); + } + + if (this.curveMCalculator != null) + { + value = this.curveMCalculator.Calculate(value); + } + + if (this.matrixCalculator != null) + { + value = this.matrixCalculator.Calculate(value); + } + + if (this.curveBCalculator != null) + { + value = this.curveBCalculator.Calculate(value); + } + + return value; + + case CalculationType.BtoA: + // ICC mBA order: B, Matrix, M, CLUT, A. + if (this.curveBCalculator != null) + { + value = this.curveBCalculator.Calculate(value); + } + + if (this.matrixCalculator != null) + { + value = this.matrixCalculator.Calculate(value); + } + + if (this.curveMCalculator != null) + { + value = this.curveMCalculator.Calculate(value); + } + + if (this.clutCalculator != null) + { + value = this.clutCalculator.Calculate(value); + } + + if (this.curveACalculator != null) + { + value = this.curveACalculator.Calculate(value); + } + + return value; default: throw new InvalidOperationException("Invalid calculation type"); } } + /// + /// Creates calculators for the processing stages present in the LUT entry. + /// + /// + /// The tag entry classes already validate channel continuity, so this method only materializes the available stages. + /// private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut) { bool hasACurve = curveA != null; @@ -86,26 +125,10 @@ internal partial class LutABCalculator : IVector4Calculator bool hasMatrix = matrix3x1 != null && matrix3x3 != null; bool hasClut = clut != null; - if (hasBCurve && hasMatrix && hasMCurve && hasClut && hasACurve) - { - this.type = CalculationType.Full; - } - else if (hasBCurve && hasClut && hasACurve) - { - this.type = CalculationType.CurveClut; - } - else if (hasBCurve && hasMatrix && hasMCurve) - { - this.type = CalculationType.CurveMatrix; - } - else if (hasBCurve) - { - this.type = CalculationType.SingleCurve; - } - else - { - throw new InvalidIccProfileException("AToB or BToA tag has an invalid configuration"); - } + Guard.IsTrue( + hasACurve || hasBCurve || hasMCurve || hasMatrix || hasClut, + nameof(curveB), + "AToB or BToA tag must contain at least one processing element"); if (hasACurve) { diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs index 20df08e37..5875b74f1 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs @@ -60,7 +60,7 @@ internal abstract partial class IccConverterBase IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16), IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB), IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA), - _ => throw new InvalidIccProfileException("Invalid entry."), + _ => throw new InvalidIccProfileException($"Invalid entry {tag}."), }; private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag) diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index 9e89d24ff..2f1b15b6b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -20,82 +20,46 @@ internal sealed partial class IccDataReader public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) { this.currentIndex = (int)info.Offset; - switch (this.ReadTagDataEntryHeader()) - { - case IccTypeSignature.Chromaticity: - return this.ReadChromaticityTagDataEntry(); - case IccTypeSignature.ColorantOrder: - return this.ReadColorantOrderTagDataEntry(); - case IccTypeSignature.ColorantTable: - return this.ReadColorantTableTagDataEntry(); - case IccTypeSignature.Curve: - return this.ReadCurveTagDataEntry(); - case IccTypeSignature.Data: - return this.ReadDataTagDataEntry(info.DataSize); - case IccTypeSignature.DateTime: - return this.ReadDateTimeTagDataEntry(); - case IccTypeSignature.Lut16: - return this.ReadLut16TagDataEntry(); - case IccTypeSignature.Lut8: - return this.ReadLut8TagDataEntry(); - case IccTypeSignature.LutAToB: - return this.ReadLutAtoBTagDataEntry(); - case IccTypeSignature.LutBToA: - return this.ReadLutBtoATagDataEntry(); - case IccTypeSignature.Measurement: - return this.ReadMeasurementTagDataEntry(); - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.MultiProcessElements: - return this.ReadMultiProcessElementsTagDataEntry(); - case IccTypeSignature.NamedColor2: - return this.ReadNamedColor2TagDataEntry(); - case IccTypeSignature.ParametricCurve: - return this.ReadParametricCurveTagDataEntry(); - case IccTypeSignature.ProfileSequenceDesc: - return this.ReadProfileSequenceDescTagDataEntry(); - case IccTypeSignature.ProfileSequenceIdentifier: - return this.ReadProfileSequenceIdentifierTagDataEntry(); - case IccTypeSignature.ResponseCurveSet16: - return this.ReadResponseCurveSet16TagDataEntry(); - case IccTypeSignature.S15Fixed16Array: - return this.ReadFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.Signature: - return this.ReadSignatureTagDataEntry(); - case IccTypeSignature.Text: - return this.ReadTextTagDataEntry(info.DataSize); - case IccTypeSignature.U16Fixed16Array: - return this.ReadUFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt16Array: - return this.ReadUInt16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt32Array: - return this.ReadUInt32ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt64Array: - return this.ReadUInt64ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt8Array: - return this.ReadUInt8ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.ViewingConditions: - return this.ReadViewingConditionsTagDataEntry(); - case IccTypeSignature.Xyz: - return this.ReadXyzTagDataEntry(info.DataSize); + return this.ReadTagDataEntryHeader() switch + { + IccTypeSignature.Chromaticity => this.ReadChromaticityTagDataEntry(), + IccTypeSignature.ColorantOrder => this.ReadColorantOrderTagDataEntry(), + IccTypeSignature.ColorantTable => this.ReadColorantTableTagDataEntry(), + IccTypeSignature.Curve => this.ReadCurveTagDataEntry(), + IccTypeSignature.Data => this.ReadDataTagDataEntry(info.DataSize), + IccTypeSignature.DateTime => this.ReadDateTimeTagDataEntry(), + IccTypeSignature.Lut16 => this.ReadLut16TagDataEntry(), + IccTypeSignature.Lut8 => this.ReadLut8TagDataEntry(), + IccTypeSignature.LutAToB => this.ReadLutAtoBTagDataEntry(), + IccTypeSignature.LutBToA => this.ReadLutBtoATagDataEntry(), + IccTypeSignature.Measurement => this.ReadMeasurementTagDataEntry(), + IccTypeSignature.MultiLocalizedUnicode => this.ReadMultiLocalizedUnicodeTagDataEntry(), + IccTypeSignature.MultiProcessElements => this.ReadMultiProcessElementsTagDataEntry(), + IccTypeSignature.NamedColor2 => this.ReadNamedColor2TagDataEntry(), + IccTypeSignature.ParametricCurve => this.ReadParametricCurveTagDataEntry(), + IccTypeSignature.ProfileSequenceDesc => this.ReadProfileSequenceDescTagDataEntry(), + IccTypeSignature.ProfileSequenceIdentifier => this.ReadProfileSequenceIdentifierTagDataEntry(), + IccTypeSignature.ResponseCurveSet16 => this.ReadResponseCurveSet16TagDataEntry(), + IccTypeSignature.S15Fixed16Array => this.ReadFix16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.Signature => this.ReadSignatureTagDataEntry(), + IccTypeSignature.Text => this.ReadTextTagDataEntry(info.DataSize), + IccTypeSignature.U16Fixed16Array => this.ReadUFix16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt16Array => this.ReadUInt16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt32Array => this.ReadUInt32ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt64Array => this.ReadUInt64ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt8Array => this.ReadUInt8ArrayTagDataEntry(info.DataSize), + IccTypeSignature.ViewingConditions => this.ReadViewingConditionsTagDataEntry(), + IccTypeSignature.Xyz => this.ReadXyzTagDataEntry(info.DataSize), // V2 Types: - case IccTypeSignature.TextDescription: - return this.ReadTextDescriptionTagDataEntry(); - case IccTypeSignature.CrdInfo: - return this.ReadCrdInfoTagDataEntry(); - case IccTypeSignature.Screening: - return this.ReadScreeningTagDataEntry(); - case IccTypeSignature.UcrBg: - return this.ReadUcrBgTagDataEntry(info.DataSize); + IccTypeSignature.TextDescription => this.ReadTextDescriptionTagDataEntry(), + IccTypeSignature.CrdInfo => this.ReadCrdInfoTagDataEntry(), + IccTypeSignature.Screening => this.ReadScreeningTagDataEntry(), + IccTypeSignature.UcrBg => this.ReadUcrBgTagDataEntry(info.DataSize), // Unsupported or unknown - case IccTypeSignature.DeviceSettings: - case IccTypeSignature.NamedColor: - case IccTypeSignature.Unknown: - default: - return this.ReadUnknownTagDataEntry(info.DataSize); - } + _ => this.ReadUnknownTagDataEntry(info.DataSize), + }; } /// @@ -477,7 +441,7 @@ internal sealed partial class IccDataReader return new IccMultiLocalizedUnicodeTagDataEntry(text); - CultureInfo ReadCulture(string language, string country) + static CultureInfo ReadCulture(string language, string country) { if (string.IsNullOrWhiteSpace(language)) { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index 05be3eb5d..eaba0a045 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -110,8 +110,8 @@ public sealed partial class IccProfile : IDeepCloneable // need to copy some values because they need to be zero for the hashing Span temp = stackalloc byte[24]; data.AsSpan(profileFlagPos, 4).CopyTo(temp); - data.AsSpan(renderingIntentPos, 4).CopyTo(temp.Slice(4)); - data.AsSpan(profileIdPos, 16).CopyTo(temp.Slice(8)); + data.AsSpan(renderingIntentPos, 4).CopyTo(temp[4..]); + data.AsSpan(profileIdPos, 16).CopyTo(temp[8..]); try { @@ -131,7 +131,7 @@ public sealed partial class IccProfile : IDeepCloneable } finally { - temp.Slice(0, 4).CopyTo(data.AsSpan(profileFlagPos)); + temp[..4].CopyTo(data.AsSpan(profileFlagPos)); temp.Slice(4, 4).CopyTo(data.AsSpan(renderingIntentPos)); temp.Slice(8, 16).CopyTo(data.AsSpan(profileIdPos)); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index 9bf323263..2219c4ca0 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -64,44 +64,7 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable @@ -192,6 +155,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Compares two curve arrays, treating consistently. + /// private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) { bool thisNull = thisCurves is null; @@ -210,27 +176,63 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; + /// + /// Validates the configured processing stages and derives the external channel counts. + /// + /// + /// Stages are evaluated in ICC mAB order: A, CLUT, M, Matrix, B. + /// Sparse pipelines are valid as long as adjacent stages agree on channel counts. + /// + private (int InputChannelCount, int OutputChannelCount) GetChannelCounts() + { + // There are at most five possible mAB stages: A, CLUT, M, Matrix, and B. + List<(int Input, int Output, string Name)> stages = new(5); - private bool IsMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; + if (this.CurveA != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + stages.Add((this.CurveA.Length, this.CurveA.Length, nameof(this.CurveA))); + } - private bool IsAClutB() - => this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; + if (this.ClutValues != null) + { + stages.Add((this.ClutValues.InputChannelCount, this.ClutValues.OutputChannelCount, nameof(this.ClutValues))); + } - private bool IsB() => this.CurveB != null; + if (this.CurveM != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveM.Length, 1, 15, nameof(this.CurveM)); + stages.Add((this.CurveM.Length, this.CurveM.Length, nameof(this.CurveM))); + } + + if (this.Matrix3x3 != null || this.Matrix3x1 != null) + { + Guard.IsTrue(this.Matrix3x3 != null && this.Matrix3x1 != null, nameof(this.Matrix3x3), "Matrix must include both the 3x3 and 3x1 components"); + stages.Add((3, 3, nameof(this.Matrix3x3))); + } + if (this.CurveB != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + stages.Add((this.CurveB.Length, this.CurveB.Length, nameof(this.CurveB))); + } + + Guard.IsTrue(stages.Count > 0, nameof(this.CurveB), "AToB tag must contain at least one processing element"); + + for (int i = 1; i < stages.Count; i++) + { + Guard.IsTrue( + stages[i - 1].Output == stages[i].Input, + stages[i].Name, + $"Output channel count of {stages[i - 1].Name} does not match input channel count of {stages[i].Name}"); + } + + return (stages[0].Input, stages[^1].Output); + } + + /// + /// Verifies that every supplied curve entry is a supported one-dimensional curve type. + /// private void VerifyCurve(IccTagDataEntry[] curves, string name) { if (curves != null) @@ -240,6 +242,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Verifies the dimensions of the optional matrix components. + /// private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) { if (matrix3x1 != null) @@ -254,6 +259,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Creates the one-dimensional matrix vector when present. + /// private static Vector3? CreateMatrix3x1(float[] matrix) { if (matrix is null) @@ -264,6 +272,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Creates the three-by-three matrix when present. + /// private static Matrix4x4? CreateMatrix3x3(float[,] matrix) { if (matrix is null) diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs index 033b80989..2df1df137 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -64,44 +64,7 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable @@ -191,6 +154,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Compares two curve arrays, treating consistently. + /// private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) { bool thisNull = thisCurves is null; @@ -209,17 +175,63 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null && this.ClutValues != null && this.CurveA != null; + /// + /// Validates the configured processing stages and derives the external channel counts. + /// + /// + /// Stages are evaluated in ICC mBA order: B, Matrix, M, CLUT, A. + /// Sparse pipelines are valid as long as adjacent stages agree on channel counts. + /// + private (int InputChannelCount, int OutputChannelCount) GetChannelCounts() + { + // There are at most five possible mBA stages: B, Matrix, M, CLUT, and A. + List<(int Input, int Output, string Name)> stages = new(5); - private bool IsBMatrixM() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null; + if (this.CurveB != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + stages.Add((this.CurveB.Length, this.CurveB.Length, nameof(this.CurveB))); + } - private bool IsBClutA() - => this.CurveB != null && this.ClutValues != null && this.CurveA != null; + if (this.Matrix3x3 != null || this.Matrix3x1 != null) + { + Guard.IsTrue(this.Matrix3x3 != null && this.Matrix3x1 != null, nameof(this.Matrix3x3), "Matrix must include both the 3x3 and 3x1 components"); + stages.Add((3, 3, nameof(this.Matrix3x3))); + } - private bool IsB() => this.CurveB != null; + if (this.CurveM != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveM.Length, 1, 15, nameof(this.CurveM)); + stages.Add((this.CurveM.Length, this.CurveM.Length, nameof(this.CurveM))); + } + + if (this.ClutValues != null) + { + stages.Add((this.ClutValues.InputChannelCount, this.ClutValues.OutputChannelCount, nameof(this.ClutValues))); + } + if (this.CurveA != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + stages.Add((this.CurveA.Length, this.CurveA.Length, nameof(this.CurveA))); + } + + Guard.IsTrue(stages.Count > 0, nameof(this.CurveB), "BToA tag must contain at least one processing element"); + + for (int i = 1; i < stages.Count; i++) + { + Guard.IsTrue( + stages[i - 1].Output == stages[i].Input, + stages[i].Name, + $"Output channel count of {stages[i - 1].Name} does not match input channel count of {stages[i].Name}"); + } + + return (stages[0].Input, stages[^1].Output); + } + + /// + /// Verifies that every supplied curve entry is a supported one-dimensional curve type. + /// private void VerifyCurve(IccTagDataEntry[] curves, string name) { if (curves != null) @@ -229,6 +241,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Verifies the dimensions of the optional matrix components. + /// private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) { if (matrix3x1 != null) @@ -243,6 +258,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Creates the one-dimensional matrix vector when present. + /// private static Vector3? CreateMatrix3x1(float[] matrix) { if (matrix is null) @@ -253,6 +271,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Creates the three-by-three matrix when present. + /// private static Matrix4x4? CreateMatrix3x3(float[,] matrix) { if (matrix is null) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 3fd55eb91..d047ed235 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -418,6 +418,21 @@ public partial class JpegDecoderTests image.CompareToReferenceOutput(provider); } + [Theory] + [WithFile(TestImages.Jpeg.ICC.Issue3064, PixelTypes.Rgba32)] + public void Decode_RGB_ICC_Jpeg_Issue3064(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + JpegDecoderOptions options = new() + { + GeneralOptions = new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert } + }; + + using Image image = provider.GetImage(JpegDecoder.Instance, options); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + // https://github.com/SixLabors/ImageSharp/issues/2948 [Theory] [WithFile(TestImages.Jpeg.Issues.Issue2948, PixelTypes.Rgb24)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 764954cae..fab1b2891 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -232,6 +232,7 @@ public static class TestImages public const string SRgbGray = "Jpg/icc-profiles/sRGB_Gray.jpg"; public const string Perceptual = "Jpg/icc-profiles/Perceptual.jpg"; public const string PerceptualcLUTOnly = "Jpg/icc-profiles/Perceptual-cLUT-only.jpg"; + public const string Issue3064 = "Jpg/icc-profiles/issue-3064.jpg"; } public static class Progressive diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png new file mode 100644 index 000000000..12692cb9e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73497e1eaddaa86cc915fe59a177e9ab6423d725ab4e6af21c8414c9edc6ceaf +size 347 diff --git a/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg b/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg new file mode 100644 index 000000000..477372c03 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e02fff450519423fd5746e610d65bd7296553252567e93de9c051250139e8adc +size 27537 From 4fc4d660fadccb4737528319e625a82f0cc23ea4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 23:02:20 +1000 Subject: [PATCH 036/240] Respond to feedback --- .../ColorProfiles/Icc/Calculators/LutABCalculator.cs | 2 +- .../Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs | 4 ++-- .../Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs index 83e51206a..10ac6e596 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs @@ -127,7 +127,7 @@ internal partial class LutABCalculator : IVector4Calculator Guard.IsTrue( hasACurve || hasBCurve || hasMCurve || hasMatrix || hasClut, - nameof(curveB), + "entry", "AToB or BToA tag must contain at least one processing element"); if (hasACurve) diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index 2219c4ca0..77bc45bd4 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -128,7 +128,7 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable Date: Sat, 7 Mar 2026 10:52:14 +1000 Subject: [PATCH 037/240] JPEG - Throw explicit ImageContentException on missing marker. --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 5 +++++ tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 7825955e7..d4517e9f1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -519,6 +519,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData fileMarker = FindNextFileMarker(stream); } + if (!metadataOnly && this.Frame is null) + { + JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); + } + this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d047ed235..36847536b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -448,4 +448,13 @@ public partial class JpegDecoderTests [InlineData(TestImages.Jpeg.Issues.Issue2948)] public void Issue2948_No_SOS_Identify_Throws_InvalidImageContentException(string imagePath) => Assert.Throws(() => _ = Image.Identify(TestFile.Create(imagePath).Bytes)); + + [Fact] + public void Issue_3071_Decode_TruncatedJpeg_Throws_InvalidImageContentException() + => Assert.Throws(() => + { + // SOI marker (FF D8) + garbage bytes — only 11 bytes + byte[] data = [0xFF, 0xD8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + using Image image = Image.Load(data); + }); } From dd827571d1ad31aab9e0492f2f8413230c6ac3ee Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 7 Mar 2026 12:27:06 +1000 Subject: [PATCH 038/240] Replace AntialiasSubpixelDepth with AntialiasThreshold --- src/ImageSharp/GraphicsOptions.cs | 46 ++++++++++--------- .../ImageSharp.Tests/GraphicsOptionsTests.cs | 32 ++++++------- .../TestUtilities/GraphicsOptionsComparer.cs | 12 ++--- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index dc3d17902..e05659651 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -6,11 +6,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp; /// -/// Options for influencing the drawing functions. +/// Provides configuration for controlling how graphics operations are rendered, +/// including antialiasing, pixel blending, alpha composition, and coverage thresholding. /// public class GraphicsOptions : IDeepCloneable { - private int antialiasSubpixelDepth = 16; + private float antialiasThreshold = .5F; private float blendPercentage = 1F; /// @@ -24,61 +25,62 @@ public class GraphicsOptions : IDeepCloneable { this.AlphaCompositionMode = source.AlphaCompositionMode; this.Antialias = source.Antialias; - this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; + this.AntialiasThreshold = source.AntialiasThreshold; this.BlendPercentage = source.BlendPercentage; this.ColorBlendingMode = source.ColorBlendingMode; } /// /// Gets or sets a value indicating whether antialiasing should be applied. - /// Defaults to true. + /// When , edges are rendered with smooth sub-pixel coverage. + /// When , coverage is snapped to binary (fully opaque or fully transparent) + /// using as the cutoff. + /// Defaults to . /// public bool Antialias { get; set; } = true; /// - /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. - /// Defaults to 16. + /// Gets or sets the coverage threshold used when is . + /// Pixels with antialiased coverage above this value are rendered as fully opaque; + /// pixels below are discarded. Valid range is 0 to 1. Lower values preserve more + /// thin features at small sizes. Defaults to 0.5F. /// - public int AntialiasSubpixelDepth + public float AntialiasThreshold { - get - { - return this.antialiasSubpixelDepth; - } + get => this.antialiasThreshold; set { - Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); - this.antialiasSubpixelDepth = value; + Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.AntialiasThreshold)); + this.antialiasThreshold = value; } } /// - /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation. - /// Range 0..1; Defaults to 1. + /// Gets or sets the blending percentage applied to the drawing operation. + /// A value of 1.0 applies the operation at full strength; 0.0 makes it invisible. + /// Valid range is 0 to 1. Defaults to 1.0F. /// public float BlendPercentage { - get - { - return this.blendPercentage; - } + get => this.blendPercentage; set { - Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); + Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.BlendPercentage)); this.blendPercentage = value; } } /// - /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. + /// Gets or sets the color blending mode used to combine source and destination pixel colors. /// Defaults to . /// public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; /// - /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation + /// Gets or sets the alpha composition mode that determines how source and destination alpha + /// channels are combined using Porter-Duff operators. /// Defaults to . /// public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index 0ccb80d3f..1b87819b6 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -13,7 +13,7 @@ public class GraphicsOptionsTests private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); [Fact] - public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + public void CloneGraphicsOptionsIsNotNull() => Assert.NotNull(this.cloneGraphicsOptions); [Fact] public void DefaultGraphicsOptionsAntialias() @@ -23,35 +23,35 @@ public class GraphicsOptionsTests } [Fact] - public void DefaultGraphicsOptionsAntialiasSuppixelDepth() + public void DefaultGraphicsOptionsAntialiasThreshold() { - const int Expected = 16; - Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); - Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); + const float expected = .5F; + Assert.Equal(expected, this.newGraphicsOptions.AntialiasThreshold); + Assert.Equal(expected, this.cloneGraphicsOptions.AntialiasThreshold); } [Fact] public void DefaultGraphicsOptionsBlendPercentage() { - const float Expected = 1F; - Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); - Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); + const float expected = 1F; + Assert.Equal(expected, this.newGraphicsOptions.BlendPercentage); + Assert.Equal(expected, this.cloneGraphicsOptions.BlendPercentage); } [Fact] public void DefaultGraphicsOptionsColorBlendingMode() { - const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; - Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); + const PixelColorBlendingMode expected = PixelColorBlendingMode.Normal; + Assert.Equal(expected, this.newGraphicsOptions.ColorBlendingMode); + Assert.Equal(expected, this.cloneGraphicsOptions.ColorBlendingMode); } [Fact] public void DefaultGraphicsOptionsAlphaCompositionMode() { - const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; - Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); + const PixelAlphaCompositionMode expected = PixelAlphaCompositionMode.SrcOver; + Assert.Equal(expected, this.newGraphicsOptions.AlphaCompositionMode); + Assert.Equal(expected, this.cloneGraphicsOptions.AlphaCompositionMode); } [Fact] @@ -61,7 +61,7 @@ public class GraphicsOptionsTests { AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, Antialias = false, - AntialiasSubpixelDepth = 23, + AntialiasThreshold = .33F, BlendPercentage = .25F, ColorBlendingMode = PixelColorBlendingMode.HardLight, }; @@ -79,7 +79,7 @@ public class GraphicsOptionsTests actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; actual.Antialias = false; - actual.AntialiasSubpixelDepth = 23; + actual.AntialiasThreshold = .67F; actual.BlendPercentage = .25F; actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs index 2a7b42f6b..649da425e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -6,13 +6,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities; public class GraphicsOptionsComparer : IEqualityComparer { public bool Equals(GraphicsOptions x, GraphicsOptions y) - { - return x.AlphaCompositionMode == y.AlphaCompositionMode - && x.Antialias == y.Antialias - && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth - && x.BlendPercentage == y.BlendPercentage - && x.ColorBlendingMode == y.ColorBlendingMode; - } + => x.AlphaCompositionMode == y.AlphaCompositionMode + && x.Antialias == y.Antialias + && x.AntialiasThreshold == y.AntialiasThreshold + && x.BlendPercentage == y.BlendPercentage + && x.ColorBlendingMode == y.ColorBlendingMode; public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); } From 4b983db6b260f9ff581004c8bbeff9bb760786d6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 8 Mar 2026 17:17:49 +0100 Subject: [PATCH 039/240] Add check, if Offset is greater then stream length when reading colorMapSize --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index a1de790c3..230526fd4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1548,6 +1548,12 @@ internal sealed class BmpDecoderCore : ImageDecoderCore case BmpFileMarkerType.Bitmap: if (this.fileHeader.HasValue) { + if (this.fileHeader.Value.Offset > stream.Length) + { + BmpThrowHelper.ThrowInvalidImageContentException( + $"Pixel data offset {this.fileHeader.Value.Offset} exceeds file size {stream.Length}."); + } + colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; } else From 7d608dd08725dc0bd5ebd367cdf69cbd2f9abc34 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 8 Mar 2026 17:18:22 +0100 Subject: [PATCH 040/240] Add test case for issue #3074 --- .../Formats/Bmp/BmpDecoderTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 6ebe1bf4e..caa6c507d 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -572,6 +572,7 @@ public class BmpDecoderTests Assert.IsType(ex.InnerException); } + // https://github.com/SixLabors/ImageSharp/issues/3067 [Fact] public void BmpDecoder_ThrowsException_Issue3067() { @@ -594,4 +595,32 @@ public class BmpDecoderTests using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); }); } + + // https://github.com/SixLabors/ImageSharp/issues/3074 + [Fact] + public void BmpDecoder_ThrowsException_Issue3074() + { + // Crafted BMP: pixel data offset = 0x7FFFFFFF, actual file = 35 bytes + byte[] data = + [ + 0x42, 0x4D, // "BM" signature + 0x3A, 0x00, 0x00, 0x00, // file size: 58 + 0x00, 0x00, 0x00, 0x00, // reserved + 0xFF, 0xFF, 0xFF, 0x7F, // pixel offset: 0x7FFFFFFF (2,147,483,647) + 0x28, 0x00, 0x00, 0x00, // DIB header size: 40 + 0x01, 0x00, 0x00, 0x00, // width: 1 + 0x01, 0xFF, 0x00, 0x00, // height: 65281 + 0x01, 0x00, // color planes: 1 + 0x08, 0x00, // bits per pixel: 8 + 0x00, 0x00, 0x00, 0x00, // compression: RGB + 0x00, 0x00, 0x00 // (truncated) + ]; + + using MemoryStream stream = new(data); + + Assert.Throws(() => + { + using Image image = Image.Load(stream); + }); + } } From 7632f8e9414087180ad6f2bd6d01db89a2482811 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 9 Mar 2026 20:59:12 +1000 Subject: [PATCH 041/240] Add Matrix4x4 transform overloads and tests --- src/ImageSharp/Primitives/Point.cs | 12 ++++ src/ImageSharp/Primitives/PointF.cs | 12 ++++ src/ImageSharp/Primitives/Rectangle.cs | 14 +++++ src/ImageSharp/Primitives/RectangleF.cs | 14 +++++ .../Transforms/TransformUtilities.cs | 16 +++-- .../Primitives/PointFTests.cs | 63 +++++++++++++++++++ .../ImageSharp.Tests/Primitives/PointTests.cs | 45 +++++++++++++ .../Primitives/RectangleFTests.cs | 43 +++++++++++++ .../Primitives/RectangleTests.cs | 33 ++++++++++ 9 files changed, 247 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs index 8627fe980..766085547 100644 --- a/src/ImageSharp/Primitives/Point.cs +++ b/src/ImageSharp/Primitives/Point.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp; @@ -234,6 +235,17 @@ public struct Point : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); + /// + /// Transforms a point by a specified 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Transform(Point point, Matrix4x4 matrix) + => Round(TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix)); + /// /// Deconstructs this point into two integers. /// diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs index 35a506bb4..7979c0af0 100644 --- a/src/ImageSharp/Primitives/PointF.cs +++ b/src/ImageSharp/Primitives/PointF.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp; @@ -246,6 +247,17 @@ public struct PointF : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); + /// + /// Transforms a point by a specified 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Transform(PointF point, Matrix4x4 matrix) + => TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix); + /// /// Deconstructs this point into two floats. /// diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index e2ae5071e..649496441 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -266,6 +266,20 @@ public struct Rectangle : IEquatable return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); } + /// + /// Transforms a rectangle by the given 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed rectangle. + public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix) + { + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } + /// /// Converts a to a by performing a truncate operation on all the coordinates. /// diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs index 68add77d0..a66e3fcad 100644 --- a/src/ImageSharp/Primitives/RectangleF.cs +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -241,6 +241,20 @@ public struct RectangleF : IEquatable return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); } + /// + /// Transforms a rectangle by the given 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed . + public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix) + { + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = PointF.Transform(rectangle.Location, matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } + /// /// Creates a rectangle that represents the union between and . /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs index 17bdeadde..5c7de1710 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -69,11 +69,17 @@ internal static class TransformUtilities [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) { - // The w component (v4.W) resulting from the transformation can be less than 0 in certain cases, - // such as when the point is transformed behind the camera in a perspective projection. - // However, in many 2D contexts, negative w values are not meaningful and could cause issues - // like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure - // we don't divide by a very small or negative number, effectively treating any negative w as epsilon. + // Transforms the 2D point (x, y) as the homogeneous coordinate (x, y, 0, 1) and + // performs the perspective divide (X/W, Y/W) to project back into Cartesian 2D space. + // + // For affine matrices (M14=0, M24=0, M34=0, M44=1) W is always 1 and the divide + // is a no-op, producing the same result as Vector2.Transform(v, Matrix4x4).AsVector2() + // (the approach used by .NET 10+). + // + // For projective matrices (taper, quad distortion) W varies per point and the divide + // is essential for correct perspective mapping. W <= 0 means the point has crossed the + // vanishing line of the projection; clamping to epsilon avoids division by zero or + // negative values that would flip/mirror the output. const float epsilon = 0.0000001F; Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); diff --git a/tests/ImageSharp.Tests/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs index 8b574daf0..bea0f802f 100644 --- a/tests/ImageSharp.Tests/Primitives/PointFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointFTests.cs @@ -133,6 +133,69 @@ public class PointFTests Assert.Equal(new PointF(30, 30), pout); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + PointF p = new(13, 17); + Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); + Matrix4x4 m4 = new(m3); + + PointF r3 = PointF.Transform(p, m3); + PointF r4 = PointF.Transform(p, m4); + + Assert.Equal(r3.X, r4.X, ApproximateFloatComparer); + Assert.Equal(r3.Y, r4.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + PointF p = new(42.5F, -17.3F); + PointF result = PointF.Transform(p, Matrix4x4.Identity); + + Assert.Equal(p.X, result.X, ApproximateFloatComparer); + Assert.Equal(p.Y, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + PointF p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + PointF result = PointF.Transform(p, m); + + Assert.Equal(15F, result.X, ApproximateFloatComparer); + Assert.Equal(17F, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Scale() + { + PointF p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1); + PointF result = PointF.Transform(p, m); + + Assert.Equal(20F, result.X, ApproximateFloatComparer); + Assert.Equal(60F, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Projective() + { + // A taper matrix with M14 != 0 produces W != 1, requiring perspective divide. + PointF p = new(100, 50); + Matrix4x4 m = Matrix4x4.Identity; + m.M14 = 0.005F; // perspective component + + PointF result = PointF.Transform(p, m); + + // W = x*M14 + M44 = 100*0.005 + 1 = 1.5 + // X = x*M11 + M41 = 100, Y = y*M22 + M42 = 50 + // result = (100/1.5, 50/1.5) + Assert.Equal(100F / 1.5F, result.X, ApproximateFloatComparer); + Assert.Equal(50F / 1.5F, result.Y, ApproximateFloatComparer); + } + [Theory] [InlineData(float.MaxValue, float.MinValue)] [InlineData(float.MinValue, float.MaxValue)] diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs index 3ad2a83b3..22c18a147 100644 --- a/tests/ImageSharp.Tests/Primitives/PointTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -174,6 +174,51 @@ public class PointTests Assert.Equal(new Point(30, 30), pout); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + Point p = new(13, 17); + Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); + Matrix4x4 m4 = new(m3); + + Point r3 = Point.Transform(p, m3); + Point r4 = Point.Transform(p, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + Point p = new(42, -17); + Point result = Point.Transform(p, Matrix4x4.Identity); + + Assert.Equal(p, result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + Point p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + Point result = Point.Transform(p, m); + + Assert.Equal(new Point(15, 17), result); + } + + [Fact] + public void TransformMatrix4x4_Projective() + { + Point p = new(100, 50); + Matrix4x4 m = Matrix4x4.Identity; + m.M14 = 0.005F; + + Point result = Point.Transform(p, m); + + // W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) => rounded + Assert.Equal(Point.Round(new PointF(100F / 1.5F, 50F / 1.5F)), result); + } + [Theory] [InlineData(int.MaxValue, int.MinValue)] [InlineData(int.MinValue, int.MinValue)] diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs index 4122daaa5..e3a13618b 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using System.Numerics; namespace SixLabors.ImageSharp.Tests; @@ -243,6 +244,48 @@ public class RectangleFTests Assert.Equal(expectedRect, r1); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3); + Matrix4x4 m4 = new(m3); + + RectangleF r3 = RectangleF.Transform(rect, m3); + RectangleF r4 = RectangleF.Transform(rect, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + RectangleF rect = new(10, 20, 100, 50); + RectangleF result = RectangleF.Transform(rect, Matrix4x4.Identity); + + Assert.Equal(rect, result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(new RectangleF(15, 17, 100, 50), result); + } + + [Fact] + public void TransformMatrix4x4_Scale() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(new RectangleF(20, 60, 200, 150), result); + } + [Fact] public void ToStringTest() { diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs index 2800852af..104bce754 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using System.Numerics; namespace SixLabors.ImageSharp.Tests; @@ -294,6 +295,38 @@ public class RectangleTests Assert.Equal(expectedRect, r1); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3); + Matrix4x4 m4 = new(m3); + + RectangleF r3 = Rectangle.Transform(rect, m3); + RectangleF r4 = Rectangle.Transform(rect, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + Rectangle rect = new(10, 20, 100, 50); + RectangleF result = Rectangle.Transform(rect, Matrix4x4.Identity); + + Assert.Equal(new RectangleF(10, 20, 100, 50), result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + RectangleF result = Rectangle.Transform(rect, m); + + Assert.Equal(new RectangleF(15, 17, 100, 50), result); + } + [Fact] public void ToStringTest() { From 96fa49feea61cd4dff338d88870693d4bf8ebb9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:00:44 +0000 Subject: [PATCH 042/240] Bump actions/upload-artifact from 6 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 2 +- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ab6f65366..ace6a4306 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -209,7 +209,7 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index ae7302bdc..16ae0317d 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -86,7 +86,7 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip From be852be4744a8eb2981d8167e4c99fa039cc21a4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Mar 2026 17:29:43 +0100 Subject: [PATCH 043/240] Add check for span length in parse png physical chunk --- src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs | 5 +++++ src/ImageSharp/Formats/Png/PngThrowHelper.cs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs index 8af0ac8ca..cce4d16b8 100644 --- a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs +++ b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs @@ -46,6 +46,11 @@ internal readonly struct PngPhysical /// The parsed PhysicalChunkData. public static PngPhysical Parse(ReadOnlySpan data) { + if (data.Length < 9) + { + PngThrowHelper.ThrowInvalidImageContentException("pHYs chunk is too short"); + } + uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]); uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); byte unit = data[8]; diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs index 8dc70e1d9..80c51ef6e 100644 --- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs +++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Png; internal static class PngThrowHelper { [DoesNotReturn] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) - => throw new InvalidImageContentException(errorMessage, innerException); + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); [DoesNotReturn] public static void ThrowInvalidHeader() => throw new InvalidImageContentException("PNG Image must contain a header chunk and it must be located before any other chunks."); From 93b9345ab878fdfbbbbd5272730d116937dd3a46 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Mar 2026 17:39:40 +0100 Subject: [PATCH 044/240] Add test case for #3078 --- .../Formats/Png/PngDecoderTests.Chunks.cs | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 8dfa98748..c31fa5831 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -6,6 +6,7 @@ using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.Memory.TestStructs; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png; @@ -20,40 +21,40 @@ public partial class PngDecoderTests private static readonly byte[] Raw1X1PngIhdrAndpHYs = [ // PNG Identifier - 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, - // IHDR - 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, - 0x00, 0x00, 0x00, + // IHDR + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, + 0x00, 0x00, 0x00, - // IHDR CRC - 0x90, 0x77, 0x53, 0xDE, + // IHDR CRC + 0x90, 0x77, 0x53, 0xDE, - // pHYS - 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, - 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + // pHYS + 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, + 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, - // pHYS CRC - 0xC7, 0x6F, 0xA8, 0x64 + // pHYS CRC + 0xC7, 0x6F, 0xA8, 0x64 ]; // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. private static readonly byte[] Raw1X1PngIdatAndIend = [ // IDAT - 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, - 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, + 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x01, - // IDAT CRC - 0x5C, 0xCD, 0xFF, 0x69, + // IDAT CRC + 0x5C, 0xCD, 0xFF, 0x69, - // IEND - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + // IEND + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, - // IEND CRC - 0xAE, 0x42, 0x60, 0x82 + // IEND CRC + 0xAE, 0x42, 0x60, 0x82 ]; [Theory] @@ -77,6 +78,21 @@ public partial class PngDecoderTests } } + // https://github.com/SixLabors/ImageSharp/issues/3078 + [Fact] + public void Decode_TruncatedPhysChunk_ExceptionIsThrown() + { + // 24 bytes — PNG signature + truncated pHYs chunk + byte[] payload = Convert.FromHexString( + "89504e470d0a1a0a3030303070485973" + + "3030303030303030"); + + using MemoryStream stream = new(payload); + InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); + + Assert.Equal("pHYs chunk is too short", exception.Message); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From 60c556cd2a096288bcff95172105eb5d55d56ca0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 9 Mar 2026 17:59:40 +0100 Subject: [PATCH 045/240] Remove not used using, change ImageFormatException to InvalidImageContentException --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index c31fa5831..0b46e7f87 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -6,7 +6,6 @@ using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using static SixLabors.ImageSharp.Tests.Memory.TestStructs; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png; @@ -71,7 +70,7 @@ public partial class PngDecoderTests WriteChunk(memStream, chunkName); WriteDataChunk(memStream); - ImageFormatException exception = + InvalidImageContentException exception = Assert.Throws(() => PngDecoder.Instance.Decode(DecoderOptions.Default, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); From b0f81fac2d84274da18862a2ab8f41ce596ae3b4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 10 Mar 2026 12:04:21 +1000 Subject: [PATCH 046/240] Add Clear method to matrix builders. --- .../Processing/AffineTransformBuilder.cs | 10 +++++ .../Processing/ProjectiveTransformBuilder.cs | 10 +++++ .../Transforms/AffineTransformBuilderTests.cs | 3 ++ .../ProjectiveTransformBuilderTests.cs | 3 ++ .../Transforms/TransformBuilderTestBase.cs | 44 +++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index c1e11a1d4..84d0d2ea1 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -349,6 +349,16 @@ public class AffineTransformBuilder internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix3x2 matrix) => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); + /// + /// Clears all accumulated transform matrices, resetting the builder to its initial state. + /// + /// The . + public AffineTransformBuilder Clear() + { + this.transformMatrixFactories.Clear(); + return this; + } + private static void CheckDegenerate(Matrix3x2 matrix) { if (TransformUtilities.IsDegenerate(matrix)) diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index e40a307a3..74da44040 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -397,6 +397,16 @@ public class ProjectiveTransformBuilder internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix4x4 matrix) => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); + /// + /// Clears all accumulated transform matrices, resetting the builder to its initial state. + /// + /// The . + public ProjectiveTransformBuilder Clear() + { + this.transformMatrixFactories.Clear(); + return this; + } + private static void CheckDegenerate(Matrix4x4 matrix) { if (TransformUtilities.IsDegenerate(matrix)) diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 232571d4d..d0e4cf566 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -58,6 +58,9 @@ public class AffineTransformBuilderTests : TransformBuilderTestBase builder.PrependTranslation(translate); + protected override void ClearBuilder(AffineTransformBuilder builder) + => builder.Clear(); + protected override Vector2 Execute( AffineTransformBuilder builder, Rectangle rectangle, diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index a0380033f..dcc653316 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -55,6 +55,9 @@ public class ProjectiveTransformBuilderTests : TransformBuilderTestBase builder.PrependRotationRadians(radians, origin); + protected override void ClearBuilder(ProjectiveTransformBuilder builder) + => builder.Clear(); + protected override Vector2 Execute( ProjectiveTransformBuilder builder, Rectangle rectangle, diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 73215b1c6..12f8326fc 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -248,6 +248,48 @@ public abstract class TransformBuilderTestBase }); } + [Fact] + public void Clear_ResetsBuilderToIdentity() + { + Size size = new(100, 100); + Rectangle rectangle = new(Point.Empty, size); + Vector2 source = new(10, 20); + + TBuilder builder = this.CreateBuilder(); + + // Apply a transform that changes the point. + this.AppendScale(builder, new SizeF(2, 3)); + this.AppendTranslation(builder, new PointF(50, 50)); + Vector2 transformed = this.Execute(builder, rectangle, source); + Assert.NotEqual(source, transformed, Comparer); + + // Clear and verify the builder produces identity behavior. + this.ClearBuilder(builder); + Vector2 afterClear = this.Execute(builder, rectangle, source); + Assert.Equal(source, afterClear, Comparer); + } + + [Fact] + public void Clear_AllowsReuse() + { + Size size = new(100, 100); + Rectangle rectangle = new(Point.Empty, size); + Vector2 source = new(10, 20); + + TBuilder builder = this.CreateBuilder(); + + // First transform: scale by 2. + this.AppendScale(builder, new SizeF(2, 2)); + Vector2 scaled = this.Execute(builder, rectangle, source); + Assert.Equal(new Vector2(20, 40), scaled, Comparer); + + // Clear and apply a different transform: translate. + this.ClearBuilder(builder); + this.AppendTranslation(builder, new PointF(5, 10)); + Vector2 translated = this.Execute(builder, rectangle, source); + Assert.Equal(new Vector2(15, 30), translated, Comparer); + } + protected abstract TBuilder CreateBuilder(); protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); @@ -282,5 +324,7 @@ public abstract class TransformBuilderTestBase protected abstract void PrependTranslation(TBuilder builder, PointF translate); + protected abstract void ClearBuilder(TBuilder builder); + protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } From fb7464e10f915b66f5959bab415d5b8047f29789 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 10 Mar 2026 12:09:00 +1000 Subject: [PATCH 047/240] Update README.md --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dc3073479..4d4375dbf 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,14 @@ SixLabors.ImageSharp [![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp/actions) [![codecov](https://codecov.io/gh/SixLabors/ImageSharp/graph/badge.svg?token=g2WJwz770q)](https://codecov.io/gh/SixLabors/ImageSharp) [![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) -[![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) -### **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API. +### **ImageSharp** is a high-performance, fully managed, cross-platform 2D graphics API. -ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics library. -Designed to simplify image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API. +ImageSharp is a mature, fully featured, high-performance image processing and graphics library for .NET, built for workloads across device, cloud, and embedded/IoT scenarios. -ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations. +Designed from the ground up to balance performance, portability, and ease of use, ImageSharp provides a powerful yet approachable API for common image processing tasks, along with the low-level building blocks needed to extend the library for specialized workflows. Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. From ba01f9c125802bd5ca87d3763dffd45e1ee3ca54 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:36:55 +0100 Subject: [PATCH 048/240] Add check in ReadCompressedTextChunk() for enough data after keyword end --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 271474a7e..42480a30d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1402,26 +1402,31 @@ internal sealed class PngDecoderCore : ImageDecoderCore return; } - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + int keywordEnd = data.IndexOf((byte)0); + if (keywordEnd is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) { return; } - byte compressionMethod = data[zeroIndex + 1]; + if (keywordEnd < 0 || keywordEnd + 2 > data.Length) + { + return; // Not enough data for keyword + null + compression method. + } + + byte compressionMethod = data[keywordEnd + 1]; if (compressionMethod != 0) { // Only compression method 0 is supported (zlib datastream with deflate compression). return; } - ReadOnlySpan keywordBytes = data[..zeroIndex]; + ReadOnlySpan keywordBytes = data[..keywordEnd]; if (!TryReadTextKeyword(keywordBytes, out string name)) { return; } - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + ReadOnlySpan compressedData = data[(keywordEnd + 2)..]; if (this.TryDecompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed) && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) From eab4147235ecef95c0ce6893614c852f8ce10e68 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:37:30 +0100 Subject: [PATCH 049/240] Add test case for not enough data after keyword end --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 0b46e7f87..0e62fd12b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -92,6 +92,29 @@ public partial class PngDecoderTests Assert.Equal("pHYs chunk is too short", exception.Message); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_CompressedTxtChunk_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 122, 84, 88, 116, // chunk type zTXt + 1, 0, // truncated data + 100, 138, 166, 20, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From fca50b9678ae00e0859cb49a37f2385dc554624c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:44:44 +0100 Subject: [PATCH 050/240] Add check for enough data in ReadInternationalTextChunk() --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 42480a30d..eadd2a43c 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1937,6 +1937,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore return; } + if (zeroIndexKeyword < 0 || zeroIndexKeyword + 4 > data.Length) + { + return; // Not enough data for keyword + null + flag + method + language. + } + byte compressionFlag = data[zeroIndexKeyword + 1]; if (compressionFlag is not (0 or 1)) { From 457436d970c184dcc74f24901b985e390f8c0257 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:45:19 +0100 Subject: [PATCH 051/240] Add test case for not enough data reading InternationalText chunk --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 0e62fd12b..d44a9a616 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -115,6 +115,29 @@ public partial class PngDecoderTests using Image image = Image.Load(stream); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 1, 0, // truncated data + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From 52c74b563ef6ddf15903c2257c05b2b835dbe87c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 17:22:09 +0100 Subject: [PATCH 052/240] Add check, if translatedKeywordLength is < 0 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index eadd2a43c..bca682d77 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1966,6 +1966,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore int translatedKeywordStartIdx = langStartIdx + languageLength + 1; int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0); + if (translatedKeywordLength < 0) + { + return; + } + string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength)); ReadOnlySpan keywordBytes = data[..zeroIndexKeyword]; From 8d78fe0b080083aff5e6156428b6f730dc52af67 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 17:22:32 +0100 Subject: [PATCH 053/240] Add test case for truncated data after language tag --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index d44a9a616..823009b68 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -138,6 +138,29 @@ public partial class PngDecoderTests using Image image = Image.Load(stream); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedDataAfterLanguageTag_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 21, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 73, 110, 116, 101, 114, 110, 97, 116, 105, 111, 110, 97, 108, 50, 0, 0, 0, 114, 117, 115, 0, // truncated data after language tag + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From d355fc0e323cce4b95aa43da64b76d7d8abb8b37 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 11 Mar 2026 16:26:48 +0100 Subject: [PATCH 054/240] Throw Exception when alpha.Length > colorTable.Length in tRNS chunk --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index bca682d77..0a7a5d359 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1253,6 +1253,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette); Color.FromPixel(rgbTable, colorTable); + if (alpha.Length > colorTable.Length) + { + throw new InvalidImageContentException( + "The tRNS chunk contains more alpha values than there are palette entries."); + } + if (alpha.Length > 0) { // The alpha chunk may contain as many transparency entries as there are palette entries From 9c9b6124f317c85aef6424b77a43722ffc718168 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 11 Mar 2026 16:53:13 +0100 Subject: [PATCH 055/240] Add test case for alpha.Length > colorTable.Length --- .../Formats/Png/PngDecoderTests.Chunks.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 823009b68..a53bd1dd5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -161,6 +161,31 @@ public partial class PngDecoderTests using Image image = Image.Load(stream); } + [Fact] + public void Decode_tRnsChunk_WithAlphaLengthGreaterColorTableLength_ExceptionIsThrown() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 8, 3, 0, 0, 0, // data + 40, 203, 52, 187, // crc + 0, 0, 0, 6, // chunk length 6 bytes + 80, 76, 84, 69, // chunk type palettte + 255, 0, 0, 0, 255, 0, // data + 210, 135, 239, 113, // crc + 0, 0, 0, 18, // chunk length + 116, 82, 78, 83, // chunk type tRns + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, // data + 0, 0, 0, 10, // chunk length + 73, 68, 65, 84, // chunk type data + 120, 156, 99, 96, 0, 0, 0, 2, 0, 1, 72, 175, 164, 113]; // alpha.Length > colorTable.Length + + using MemoryStream stream = new(payload); + InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); + + Assert.Equal("The tRNS chunk contains more alpha values than there are palette entries.", exception.Message); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From c512b142f93f675fcbbdcb32efd5635b1c67a842 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 12 Mar 2026 10:56:20 +0100 Subject: [PATCH 056/240] If alpha length is greater then colorTable, slice length to colorTable length --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 ++-- .../ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 0a7a5d359..896218267 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1253,10 +1253,10 @@ internal sealed class PngDecoderCore : ImageDecoderCore ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette); Color.FromPixel(rgbTable, colorTable); + // The tRNS chunk must not contain more alpha values than there are palette entries. if (alpha.Length > colorTable.Length) { - throw new InvalidImageContentException( - "The tRNS chunk contains more alpha values than there are palette entries."); + alpha = alpha.Slice(0, colorTable.Length); } if (alpha.Length > 0) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index a53bd1dd5..03dc04018 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -162,7 +162,7 @@ public partial class PngDecoderTests } [Fact] - public void Decode_tRnsChunk_WithAlphaLengthGreaterColorTableLength_ExceptionIsThrown() + public void Decode_tRnsChunk_WithAlphaLengthGreaterColorTableLength_ShouldNotThrowException() { byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature 0, 0, 0, 13, // chunk length 13 bytes @@ -181,9 +181,7 @@ public partial class PngDecoderTests 120, 156, 99, 96, 0, 0, 0, 2, 0, 1, 72, 175, 164, 113]; // alpha.Length > colorTable.Length using MemoryStream stream = new(payload); - InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); - - Assert.Equal("The tRNS chunk contains more alpha values than there are palette entries.", exception.Message); + using Image image = Image.Load(stream); } private static string GetChunkTypeName(uint value) From fa52ab740fe97a6de382ee20792d2565ec36a512 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Mar 2026 18:05:55 +0100 Subject: [PATCH 057/240] Adjust exr decoder / encoder to ImageSharp changes --- ImageSharp.sln | 4 +- src/ImageSharp/Configuration.cs | 2 +- .../Decompressors/ZipExrCompression.cs | 2 +- .../Formats/OpenExr/ExrConfigurationModule.cs | 4 +- src/ImageSharp/Formats/OpenExr/ExrDecoder.cs | 36 +++--- .../Formats/OpenExr/ExrDecoderCore.cs | 86 +++++++------- .../Formats/OpenExr/ExrDecoderOptions.cs | 2 +- src/ImageSharp/Formats/OpenExr/ExrEncoder.cs | 22 +--- .../Formats/OpenExr/ExrEncoderCore.cs | 75 +++++++----- .../Formats/OpenExr/ExrHeaderAttributes.cs | 85 ++++++-------- .../Formats/OpenExr/ExrImageFormatDetector.cs | 12 +- src/ImageSharp/Formats/OpenExr/ExrMetadata.cs | 16 ++- .../Formats/OpenExr/MetadataExtensions.cs | 20 ---- .../_Generated/ImageExtensions.Save.cs | 100 +++++++++++++--- .../_Generated/ImageMetadataExtensions.cs | 21 ++++ .../Formats/_Generated/_Formats.ttinclude | 3 +- src/ImageSharp/ImageSharp.csproj | 10 +- .../PixelImplementations/Rgb96.cs | 88 ++++---------- .../PixelImplementations/Rgba128.cs | 95 +++++---------- .../Formats/Exr/ExrDecoderTests.cs | 12 +- .../Formats/Exr/ImageExtensionsTest.cs | 108 +++++++++++++++--- .../TestUtilities/TestEnvironment.Formats.cs | 2 +- 22 files changed, 440 insertions(+), 365 deletions(-) delete mode 100644 src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs diff --git a/ImageSharp.sln b/ImageSharp.sln index 7ccd92c07..13dd2fba7 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -37,8 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3 ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props src\Directory.Build.targets = src\Directory.Build.targets - src\README.md = src\README.md src\ImageSharp.ruleset = src\ImageSharp.ruleset + src\README.md = src\README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" @@ -215,6 +215,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68 ProjectSection(SolutionItems) = preProject tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg = tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg = tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg + tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg = tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg = tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg @@ -238,7 +239,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68 tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg - tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}" diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index abcbcb58f..7a2374fb6 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -226,7 +226,7 @@ public sealed class Configuration new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule(), - new ExrConfigurationModule()); + new ExrConfigurationModule(), new QoiConfigurationModule(), new IcoConfigurationModule(), new CurConfigurationModule()); diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs index e315af8e8..1797bd44b 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs @@ -29,7 +29,7 @@ internal class ZipExrCompression : ExrBaseDecompressor return left > 0 ? left : 0; }); inflateStream.AllocateNewBytes((int)this.UncompressedBytes, true); - DeflateStream dataStream = inflateStream.CompressedStream; + DeflateStream dataStream = inflateStream.CompressedStream!; int totalRead = 0; while (totalRead < buffer.Length) diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs index 0b0058805..c0322728a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs @@ -6,13 +6,13 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. /// -public sealed class ExrConfigurationModule : IConfigurationModule +public sealed class ExrConfigurationModule : IImageFormatConfigurationModule { /// public void Configure(Configuration configuration) { configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); - configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, new ExrDecoder()); + configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, ExrDecoder.Instance); configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs index dcbe80c27..84d441eb7 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs @@ -8,39 +8,41 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Image decoder for generating an image out of a OpenExr stream. /// -public class ExrDecoder : IImageDecoderSpecialized +public class ExrDecoder : ImageDecoder { + private ExrDecoder() + { + } + + /// + /// Gets the shared instance. + /// + public static ExrDecoder Instance { get; } = new(); + /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new ExrDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); + return new ExrDecoderCore(new ExrDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - - /// - Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) + //// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - Image image = new ExrDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + ExrDecoderCore decoder = new(new ExrDecoderOptions { GeneralOptions = options }); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - ImageDecoderUtilities.Resize(options.GeneralOptions, image); + ScaleToTargetSize(options, image); return image; } /// - Image IImageDecoderSpecialized.Decode(ExrDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs index 3e554fb74..6eaf56fde 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs @@ -1,5 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +#nullable disable using System.Buffers; using System.Buffers.Binary; @@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Performs the OpenExr decoding operation. /// -internal sealed class ExrDecoderCore : IImageDecoderInternals +internal sealed class ExrDecoderCore : ImageDecoderCore { /// /// Reusable buffer. @@ -43,15 +44,12 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals /// /// The options. public ExrDecoderCore(ExrDecoderOptions options) + : base(options.GeneralOptions) { - this.Options = options.GeneralOptions; this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; } - /// - public DecoderOptions Options { get; } - /// /// Gets the dimensions of the image. /// @@ -84,8 +82,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private ExrHeaderAttributes HeaderAttributes { get; set; } /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadExrHeader(stream); if (!this.IsSupportedCompression()) @@ -162,7 +159,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals for (int x = 0; x < width; x++) { HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); - color.FromVector4(pixelValue.ToVector4()); + TPixel.FromVector4(pixelValue.ToVector4()); pixelRow[x] = color; } } @@ -219,7 +216,7 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals for (int x = 0; x < width; x++) { Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - color.FromVector4(pixelValue.ToVector4()); + TPixel.FromVector4(pixelValue.ToVector4()); pixelRow[x] = color; } } @@ -366,13 +363,13 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals } /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { ExrHeaderAttributes header = this.ReadExrHeader(stream); int bitsPerPixel = this.CalculateBitsPerPixel(); - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.Width, this.Height, new ImageMetadata()); + return new ImageInfo(new Size((int)header.ScreenWindowWidth, (int)header.AspectRatio), this.metadata); } private int CalculateBitsPerPixel() @@ -430,15 +427,10 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals this.HeaderAttributes = this.ParseHeaderAttributes(stream); - if (!this.HeaderAttributes.IsValid()) - { - ExrThrowHelper.ThrowInvalidImageHeader(); - } - - this.Width = this.HeaderAttributes.DataWindow.Value.XMax - this.HeaderAttributes.DataWindow.Value.XMin + 1; - this.Height = this.HeaderAttributes.DataWindow.Value.YMax - this.HeaderAttributes.DataWindow.Value.YMin + 1; + this.Width = this.HeaderAttributes.DataWindow.XMax - this.HeaderAttributes.DataWindow.XMin + 1; + this.Height = this.HeaderAttributes.DataWindow.YMax - this.HeaderAttributes.DataWindow.YMin + 1; this.Channels = this.HeaderAttributes.Channels; - this.Compression = this.HeaderAttributes.Compression.GetValueOrDefault(); + this.Compression = this.HeaderAttributes.Compression; this.metadata = new ImageMetadata(); @@ -448,50 +440,54 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) { ExrAttribute attribute = this.ReadAttribute(stream); - ExrHeaderAttributes header = new(); + IList channels = null; + ExrBox2i? dataWindow = null; + ExrCompressionType? compression = null; + ExrBox2i? displayWindow = null; + ExrLineOrder? lineOrder = null; + float? aspectRatio = null; + float? screenWindowCenterX = null; + float? screenWindowCenterY = null; + float? screenWindowWidth = null; + uint? tileXSize = null; + uint? tileYSize = null; + int? chunkCount = null; while (!attribute.Equals(ExrAttribute.EmptyAttribute)) { switch (attribute.Name) { case ExrConstants.AttributeNames.Channels: - IList channels = this.ReadChannelList(stream, attribute.Length); - header.Channels = channels; + channels = this.ReadChannelList(stream, attribute.Length); break; case ExrConstants.AttributeNames.Compression: - header.Compression = (ExrCompressionType)stream.ReadByte(); + compression = (ExrCompressionType)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: - ExrBox2i dataWindow = this.ReadBoxInteger(stream); - header.DataWindow = dataWindow; + dataWindow = this.ReadBoxInteger(stream); break; case ExrConstants.AttributeNames.DisplayWindow: - ExrBox2i displayWindow = this.ReadBoxInteger(stream); - header.DisplayWindow = displayWindow; + displayWindow = this.ReadBoxInteger(stream); break; case ExrConstants.AttributeNames.LineOrder: - ExrLineOrder lineOrder = (ExrLineOrder)stream.ReadByte(); - header.LineOrder = lineOrder; + lineOrder = (ExrLineOrder)stream.ReadByte(); break; case ExrConstants.AttributeNames.PixelAspectRatio: - float aspectRatio = stream.ReadSingle(this.buffer); - header.AspectRatio = aspectRatio; + aspectRatio = stream.ReadSingle(this.buffer); break; case ExrConstants.AttributeNames.ScreenWindowCenter: - float screenWindowCenterX = stream.ReadSingle(this.buffer); - float screenWindowCenterY = stream.ReadSingle(this.buffer); - header.ScreenWindowCenter = new PointF(screenWindowCenterX, screenWindowCenterY); + screenWindowCenterX = stream.ReadSingle(this.buffer); + screenWindowCenterY = stream.ReadSingle(this.buffer); break; case ExrConstants.AttributeNames.ScreenWindowWidth: - float screenWindowWidth = stream.ReadSingle(this.buffer); - header.ScreenWindowWidth = screenWindowWidth; + screenWindowWidth = stream.ReadSingle(this.buffer); break; case ExrConstants.AttributeNames.Tiles: - header.TileXSize = this.ReadUnsignedInteger(stream); - header.TileYSize = this.ReadUnsignedInteger(stream); + tileXSize = this.ReadUnsignedInteger(stream); + tileYSize = this.ReadUnsignedInteger(stream); break; case ExrConstants.AttributeNames.ChunkCount: - header.ChunkCount = this.ReadSignedInteger(stream); + chunkCount = this.ReadSignedInteger(stream); break; default: // Skip unknown attribute bytes. @@ -502,6 +498,18 @@ internal sealed class ExrDecoderCore : IImageDecoderInternals attribute = this.ReadAttribute(stream); } + ExrHeaderAttributes header = new( + channels, + compression.Value, + dataWindow.Value, + displayWindow.Value, + lineOrder.Value, + aspectRatio.Value, + screenWindowWidth.Value, + new PointF(screenWindowCenterX.Value, screenWindowCenterY.Value), + tileXSize, + tileYSize, + chunkCount); return header; } diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs index f29e9cfb9..c0b419b93 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs @@ -9,5 +9,5 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; public sealed class ExrDecoderOptions : ISpecializedDecoderOptions { /// - public DecoderOptions GeneralOptions { get; set; } = new(); + public DecoderOptions GeneralOptions { get; init; } = new(); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs index 54145afda..9960efd68 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs @@ -1,34 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Image encoder for writing an image to a stream in the OpenExr Format. /// -public sealed class ExrEncoder : IImageEncoder, IExrEncoderOptions +public sealed class ExrEncoder : ImageEncoder { /// /// Gets or sets the pixel type of the image. /// public ExrPixelType? PixelType { get; set; } - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } - - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - ExrEncoderCore encoder = new(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); + ExrEncoderCore encoder = new(this, image.Configuration, image.Configuration.MemoryAllocator); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs index 21fb0ade4..740b71e12 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading.Channels; using SixLabors.ImageSharp.Formats.OpenExr.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Image encoder for writing an image to a stream in the OpenExr format. /// -internal sealed class ExrEncoderCore : IImageEncoderInternals +internal sealed class ExrEncoderCore { /// /// Reusable buffer. @@ -27,6 +28,16 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals /// private readonly MemoryAllocator memoryAllocator; + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder with options. + /// + private readonly ExrEncoder encoder; + /// /// The pixel type of the image. /// @@ -35,12 +46,14 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals /// /// Initializes a new instance of the class. /// - /// The encoder options. + /// The encoder with options. + /// The configuration. /// The memory manager. - public ExrEncoderCore(IExrEncoderOptions options, MemoryAllocator memoryAllocator) + public ExrEncoderCore(ExrEncoder encoder, Configuration configuration, MemoryAllocator memoryAllocator) { + this.configuration = configuration; + this.encoder = encoder; this.memoryAllocator = memoryAllocator; - this.pixelType = options.PixelType; } /// @@ -63,22 +76,28 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals this.pixelType ??= exrMetadata.PixelType; int width = image.Width; int height = image.Height; - var header = new ExrHeaderAttributes() - { - Compression = ExrCompressionType.None, - AspectRatio = 1.0f, - DataWindow = new ExrBox2i(0, 0, width - 1, height - 1), - DisplayWindow = new ExrBox2i(0, 0, width - 1, height - 1), - LineOrder = ExrLineOrder.IncreasingY, - ScreenWindowCenter = new PointF(0.0f, 0.0f), - ScreenWindowWidth = 1, - Channels = new List() - { - new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), - new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), - } - }; + ExrCompressionType compression = ExrCompressionType.None; + float aspectRatio = 1.0f; + ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); + ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); + ExrLineOrder lineOrder = ExrLineOrder.IncreasingY; + PointF screenWindowCenter = new(0.0f, 0.0f); + int screenWindowWidth = 1; + List channels = + [ + new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), + new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), + ]; + ExrHeaderAttributes header = new( + channels, + compression, + dataWindow, + displayWindow, + lineOrder, + aspectRatio, + screenWindowWidth, + screenWindowCenter); // Write magick bytes. BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); @@ -171,7 +190,7 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals for (int x = 0; x < width; x++) { Vector4 vector4 = pixelRowSpan[x].ToVector4(); - rgb.FromVector4(vector4); + Rgb96.FromVector4(vector4); redBuffer[x] = rgb.R; greenBuffer[x] = rgb.G; @@ -193,13 +212,13 @@ internal sealed class ExrEncoderCore : IImageEncoderInternals private void WriteHeader(Stream stream, ExrHeaderAttributes header) { this.WriteChannels(stream, header.Channels); - this.WriteCompression(stream, header.Compression.Value); - this.WriteDataWindow(stream, header.DataWindow.Value); - this.WriteDisplayWindow(stream, header.DisplayWindow.Value); - this.WritePixelAspectRatio(stream, header.AspectRatio.Value); - this.WriteLineOrder(stream, header.LineOrder.Value); - this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter.Value); - this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth.Value); + this.WriteCompression(stream, header.Compression); + this.WriteDataWindow(stream, header.DataWindow); + this.WriteDisplayWindow(stream, header.DisplayWindow); + this.WritePixelAspectRatio(stream, header.AspectRatio); + this.WriteLineOrder(stream, header.LineOrder); + this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter); + this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth); stream.WriteByte(0); } diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs index b3b3cdb60..4cec4fbc5 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs @@ -7,70 +7,51 @@ namespace SixLabors.ImageSharp.Formats.OpenExr; internal class ExrHeaderAttributes { + public ExrHeaderAttributes( + IList channels, + ExrCompressionType compression, + ExrBox2i dataWindow, + ExrBox2i displayWindow, + ExrLineOrder lineOrder, + float aspectRatio, + float screenWindowWidth, + PointF screenWindowCenter, + uint? tileXSize = null, + uint? tileYSize = null, + int? chunkCount = null) + { + this.Channels = channels; + this.Compression = compression; + this.DataWindow = dataWindow; + this.DisplayWindow = displayWindow; + this.LineOrder = lineOrder; + this.AspectRatio = aspectRatio; + this.ScreenWindowWidth = screenWindowWidth; + this.ScreenWindowCenter = screenWindowCenter; + this.TileXSize = tileXSize; + this.TileYSize = tileYSize; + this.ChunkCount = chunkCount; + } + public IList Channels { get; set; } - public ExrCompressionType? Compression { get; set; } + public ExrCompressionType Compression { get; set; } - public ExrBox2i? DataWindow { get; set; } + public ExrBox2i DataWindow { get; set; } - public ExrBox2i? DisplayWindow { get; set; } + public ExrBox2i DisplayWindow { get; set; } - public ExrLineOrder? LineOrder { get; set; } + public ExrLineOrder LineOrder { get; set; } - public float? AspectRatio { get; set; } + public float AspectRatio { get; set; } - public float? ScreenWindowWidth { get; set; } + public float ScreenWindowWidth { get; set; } - public PointF? ScreenWindowCenter { get; set; } + public PointF ScreenWindowCenter { get; set; } public uint? TileXSize { get; set; } public uint? TileYSize { get; set; } public int? ChunkCount { get; set; } - - public bool IsValid() - { - if (!this.Compression.HasValue) - { - return false; - } - - if (!this.DataWindow.HasValue) - { - return false; - } - - if (!this.DisplayWindow.HasValue) - { - return false; - } - - if (!this.LineOrder.HasValue) - { - return false; - } - - if (!this.AspectRatio.HasValue) - { - return false; - } - - if (!this.ScreenWindowWidth.HasValue) - { - return false; - } - - if (!this.ScreenWindowCenter.HasValue) - { - return false; - } - - if (this.Channels is null) - { - return false; - } - - return true; - } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs index c14ab947a..2f97e7404 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Formats.Qoi; namespace SixLabors.ImageSharp.Formats.OpenExr; @@ -13,9 +15,6 @@ public sealed class ExrImageFormatDetector : IImageFormatDetector /// public int HeaderSize => 4; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) { if (header.Length >= this.HeaderSize) @@ -26,4 +25,11 @@ public sealed class ExrImageFormatDetector : IImageFormatDetector return false; } + + /// + public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + { + format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null; + return format != null; + } } diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs index 8b090d684..9cb58e9e6 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs @@ -1,12 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats.OpenExr; /// /// Provides OpenExr specific metadata information for the image. /// -public class ExrMetadata : IDeepCloneable +public class ExrMetadata : IFormatMetadata { /// /// Initializes a new instance of the class. @@ -26,6 +29,17 @@ public class ExrMetadata : IDeepCloneable /// public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; + public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); + + public void AfterImageApply(Image destination, Matrix4x4 matrix) + where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + /// public IDeepCloneable DeepClone() => new ExrMetadata(this); + + public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + + public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); + + ExrMetadata IDeepCloneable.DeepClone() => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs b/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs deleted file mode 100644 index 2af0a57b0..000000000 --- a/src/ImageSharp/Formats/OpenExr/MetadataExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.OpenExr; -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp; - -/// -/// Extension methods for the type. -/// -public static partial class MetadataExtensions -{ - /// - /// Gets the open exr format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static ExrMetadata GetExrMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(ExrFormat.Instance); -} diff --git a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs index f3aaf6182..ca4ad0e06 100644 --- a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs @@ -7,13 +7,13 @@ using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Formats.OpenExr; namespace SixLabors.ImageSharp; @@ -1144,24 +1144,95 @@ public static partial class ImageExtensions encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance), cancellationToken); - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), - cancellationToken); + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path) => SaveAsExr(source, path, default); /// - /// Saves the image to the given stream with the Open Exr format. + /// Saves the image to the given stream with the Exr format. /// /// The image this method extends. - /// The stream to save the image to. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path) => SaveAsExrAsync(source, path, default); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsExrAsync(source, path, default, cancellationToken); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsExr(this Image source, string path, ExrEncoder encoder) => + source.Save( + path, + encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(ExrFormat.Instance)); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsExrAsync(this Image source, string path, ExrEncoder encoder, CancellationToken cancellationToken = default) + => source.SaveAsync( + path, + encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(ExrFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsExr(this Image source, Stream stream) + => SaveAsExr(source, stream, default); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static void SaveAsOpenExr(this Image source, Stream stream, ExrEncoder encoder) + public static Task SaveAsExrAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsExrAsync(source, stream, default, cancellationToken); + + /// + /// Saves the image to the given stream with the Exr format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsExr(this Image source, Stream stream, ExrEncoder encoder) => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance)); + stream, + encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(ExrFormat.Instance)); /// - /// Saves the image to the given stream with the Open Exr format. + /// Saves the image to the given stream with the Exr format. /// /// The image this method extends. /// The stream to save the image to. @@ -1169,11 +1240,10 @@ public static partial class ImageExtensions /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsOpenExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ExrFormat.Instance), - cancellationToken); - + public static Task SaveAsExrAsync(this Image source, Stream stream, ExrEncoder encoder, CancellationToken cancellationToken = default) + => source.SaveAsync( + stream, + encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(ExrFormat.Instance), + cancellationToken); } diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs index e35d00ed3..31a25fced 100644 --- a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs +++ b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs @@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Formats.OpenExr; namespace SixLabors.ImageSharp; @@ -242,6 +243,26 @@ public static class ImageMetadataExtensions /// The new public static WebpMetadata CloneWebpMetadata(this ImageMetadata source) => source.CloneFormatMetadata(WebpFormat.Instance); + /// + /// Gets the from .
+ /// If none is found, an instance is created either by conversion from the decoded image format metadata + /// or the requested format default constructor. + /// This instance will be added to the metadata for future requests. + ///
+ /// The image metadata. + /// + /// The + /// + public static ExrMetadata GetExrMetadata(this ImageMetadata source) => source.GetFormatMetadata(ExrFormat.Instance); + + /// + /// Creates a new cloned instance of from the . + /// The instance is created via + /// + /// The image metadata. + /// The new + public static ExrMetadata CloneExrMetadata(this ImageMetadata source) => source.CloneFormatMetadata(ExrFormat.Instance); + /// /// Gets the from .
diff --git a/src/ImageSharp/Formats/_Generated/_Formats.ttinclude b/src/ImageSharp/Formats/_Generated/_Formats.ttinclude index 2d6129c4c..c1c69c5b5 100644 --- a/src/ImageSharp/Formats/_Generated/_Formats.ttinclude +++ b/src/ImageSharp/Formats/_Generated/_Formats.ttinclude @@ -14,7 +14,8 @@ "Qoi", "Tga", "Tiff", - "Webp" + "Webp", + "Exr" ]; private static readonly string[] frameFormats = [ diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 2c7172387..32a5f0073 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -56,6 +56,11 @@ True InlineArray.tt + + True + True + ImageExtensions.Save.tt + True True @@ -141,11 +146,6 @@ True PorterDuffFunctions.Generated.tt - - True - True - ImageExtensions.Save.tt - diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 620e305a2..165f13b07 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -72,27 +72,10 @@ public partial struct Rgb96 : IPixel [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToScaledVector4() => this.ToVector4(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.R = (uint)(vector.X * Max); - this.G = (uint)(vector.Y * Max); - this.B = (uint)(vector.Z * Max); - } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() => new( @@ -103,71 +86,48 @@ public partial struct Rgb96 : IPixel /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static PixelOperations CreatePixelOperations() => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromScaledVector4(Vector4 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromVector4(Vector4 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromAbgr32(Abgr32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromArgb32(Argb32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromBgr24(Bgr24 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromBgra32(Bgra32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + public static Rgb96 FromL8(L8 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromL16(L16 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgb96 FromLa16(La16 source) => throw new NotImplementedException(); - /// - public override bool Equals(object obj) => obj is Rgb96 other && this.Equals(other); + public static Rgb96 FromLa32(La32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + public static Rgb96 FromRgb24(Rgb24 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public static Rgb96 FromRgba32(Rgba32 source) => throw new NotImplementedException(); - /// - public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + public static Rgb96 FromRgb48(Rgb48 source) => throw new NotImplementedException(); + + public static Rgb96 FromRgba64(Rgba64 source) => throw new NotImplementedException(); + + public static PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + + public Rgba32 ToRgba32() => throw new NotImplementedException(); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index 912a62db0..fd094be59 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -79,28 +79,6 @@ public partial struct Rgba128 : IPixel [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.R = (uint)(vector.X * Max); - this.G = (uint)(vector.Y * Max); - this.B = (uint)(vector.Z * Max); - this.A = (uint)(vector.W * Max); - } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() => new( @@ -111,71 +89,50 @@ public partial struct Rgba128 : IPixel /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static PixelOperations CreatePixelOperations() => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromScaledVector4(Vector4 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromVector4(Vector4 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromAbgr32(Abgr32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromArgb32(Argb32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromBgr24(Bgr24 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromBgra32(Bgra32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + public static Rgba128 FromL8(L8 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromL16(L16 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + public static Rgba128 FromLa16(La16 source) => throw new NotImplementedException(); - /// - public override bool Equals(object obj) => obj is Rgba128 other && this.Equals(other); + public static Rgba128 FromLa32(La32 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + public static Rgba128 FromRgb24(Rgb24 source) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + public static Rgba128 FromRgba32(Rgba32 source) => throw new NotImplementedException(); - /// - public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + public static Rgba128 FromRgb48(Rgb48 source) => throw new NotImplementedException(); + + public static Rgba128 FromRgba64(Rgba64 source) => throw new NotImplementedException(); + + public static PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + + public Rgba32 ToRgba32() => throw new NotImplementedException(); + + public Vector4 ToScaledVector4() => throw new NotImplementedException(); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 29b75bc9c..0391ab7c8 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -10,14 +10,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Exr; [ValidateDisposedMemoryAllocations] public class ExrDecoderTests { - private static ExrDecoder ExrDecoder => new(); - [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(ExrDecoder); + using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider); } @@ -27,7 +25,7 @@ public class ExrDecoderTests public void ExrDecoder_CanDecode_ZipCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(ExrDecoder); + using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider); } @@ -37,7 +35,7 @@ public class ExrDecoderTests public void ExrDecoder_CanDecode_ZipsCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(ExrDecoder); + using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider); } @@ -47,7 +45,7 @@ public class ExrDecoderTests public void ExrDecoder_CanDecode_RunLengthCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(ExrDecoder); + using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider); } @@ -57,7 +55,7 @@ public class ExrDecoderTests public void ExrDecoder_CanDecode_B44Compressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(ExrDecoder); + using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); image.CompareToOriginal(provider); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index 926f5ca90..78ce845d3 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -11,56 +11,126 @@ namespace SixLabors.ImageSharp.Tests.Formats.Exr; public class ImageExtensionsTest { [Fact] - public void SaveAsExr_Stream() + public void SaveAsExr_Path() { - using var memoryStream = new MemoryStream(); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExr_Path.exr"); - using (var image = new Image(10, 10)) + using (Image image = new(10, 10)) { - image.SaveAsOpenExr(memoryStream, new ExrEncoder()); + image.SaveAsExr(file); } - memoryStream.Position = 0; + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); - using (Image.Load(memoryStream, out IImageFormat mime)) + using (Image image = new(10, 10)) { - Assert.Equal("image/x-exr", mime.DefaultMimeType); + await image.SaveAsExrAsync(file); } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); } [Fact] - public void SaveAsExr_Stream_Encoder() + public void SaveAsExr_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); + + using (Image image = new(10, 10)) + { + image.SaveAsExr(file, new ExrEncoder()); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff"); + + using (Image image = new(10, 10)) + { + await image.SaveAsExrAsync(file, new ExrEncoder()); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public void SaveAsExr_Stream() { - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); - using (var image = new Image(10, 10)) + using (Image image = new(10, 10)) { - image.SaveAsOpenExr(memoryStream, new ExrEncoder()); + image.SaveAsExr(memoryStream); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_StreamAsync() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) { - Assert.Equal("image/x-exr", mime.DefaultMimeType); + await image.SaveAsExrAsync(memoryStream); } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); } [Fact] - public async Task SaveAsExrAsync_Stream_Encoder() + public void SaveAsExr_Stream_Encoder() { - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); - using (var image = new Image(10, 10)) + using (Image image = new(10, 10)) { - await image.SaveAsOpenExrAsync(memoryStream, new ExrEncoder()); + image.SaveAsTiff(memoryStream, new ExrEncoder()); } memoryStream.Position = 0; - using (Image.Load(memoryStream, out IImageFormat mime)) + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Stream_Encoder() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) { - Assert.Equal("image/x-exr", mime.DefaultMimeType); + await image.SaveAsExrAsync(memoryStream, new ExrEncoder()); } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 4dc3def1f..9ced51608 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -64,7 +64,7 @@ public static partial class TestEnvironment new TgaConfigurationModule(), new WebpConfigurationModule(), new TiffConfigurationModule(), - new ExrConfigurationModule()); + new ExrConfigurationModule(), new QoiConfigurationModule()); IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); From 83dba5872b154716a8c23101395e6bd8be477a02 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Mar 2026 18:10:06 +0100 Subject: [PATCH 058/240] Change exr name space to SixLabors.ImageSharp.Formats.Exr --- src/ImageSharp/Configuration.cs | 2 +- .../Compression/Decompressors/B44Compression.cs | 2 +- .../Compression/Decompressors/NoneExrCompression.cs | 2 +- .../Compression/Decompressors/RunLengthCompression.cs | 2 +- .../Compression/Decompressors/ZipExrCompression.cs | 2 +- .../{OpenExr => Exr}/Compression/ExrBaseCompression.cs | 2 +- .../{OpenExr => Exr}/Compression/ExrBaseDecompressor.cs | 2 +- .../{OpenExr => Exr}/Compression/ExrCompressionType.cs | 2 +- .../{OpenExr => Exr}/Compression/ExrDecompressorFactory.cs | 4 ++-- src/ImageSharp/Formats/{OpenExr => Exr}/ExrAttribute.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrBox2i.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrChannelInfo.cs | 2 +- .../Formats/{OpenExr => Exr}/ExrConfigurationModule.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrConstants.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoder.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoderCore.cs | 4 ++-- src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoderOptions.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrEncoder.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrEncoderCore.cs | 4 ++-- src/ImageSharp/Formats/{OpenExr => Exr}/ExrFormat.cs | 2 +- .../Formats/{OpenExr => Exr}/ExrHeaderAttributes.cs | 4 ++-- src/ImageSharp/Formats/{OpenExr => Exr}/ExrImageDataType.cs | 2 +- .../Formats/{OpenExr => Exr}/ExrImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrImageType.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrLineOrder.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrMetadata.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrPixelType.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/ExrThrowHelper.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/IExrEncoderOptions.cs | 2 +- src/ImageSharp/Formats/{OpenExr => Exr}/README.md | 0 src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs | 2 +- src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs | 2 +- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 2 +- tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs | 4 ++-- .../ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs | 2 +- 35 files changed, 39 insertions(+), 39 deletions(-) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/Decompressors/B44Compression.cs (98%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/Decompressors/NoneExrCompression.cs (89%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/Decompressors/RunLengthCompression.cs (96%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/Decompressors/ZipExrCompression.cs (96%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/ExrBaseCompression.cs (93%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/ExrBaseDecompressor.cs (95%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/ExrCompressionType.cs (97%) rename src/ImageSharp/Formats/{OpenExr => Exr}/Compression/ExrDecompressorFactory.cs (90%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrAttribute.cs (92%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrBox2i.cs (91%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrChannelInfo.cs (94%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrConfigurationModule.cs (93%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrConstants.cs (97%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoder.cs (96%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoderCore.cs (99%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrDecoderOptions.cs (87%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrEncoder.cs (93%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrEncoderCore.cs (99%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrFormat.cs (94%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrHeaderAttributes.cs (93%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrImageDataType.cs (78%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrImageFormatDetector.cs (95%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrImageType.cs (74%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrLineOrder.cs (78%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrMetadata.cs (96%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrPixelType.cs (90%) rename src/ImageSharp/Formats/{OpenExr => Exr}/ExrThrowHelper.cs (95%) rename src/ImageSharp/Formats/{OpenExr => Exr}/IExrEncoderOptions.cs (87%) rename src/ImageSharp/Formats/{OpenExr => Exr}/README.md (100%) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 7a2374fb6..6e431ba31 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs similarity index 98% rename from src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs index 55e9003bc..b8cb46814 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/B44Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class B44Compression : ExrBaseDecompressor { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs similarity index 89% rename from src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index f5f16a0fd..6b36dffa2 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class NoneExrCompression : ExrBaseDecompressor { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs similarity index 96% rename from src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs index 0838dc1dc..f1c7bb759 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs @@ -5,7 +5,7 @@ using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class RunLengthCompression : ExrBaseDecompressor { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs similarity index 96% rename from src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 1797bd44b..4d9b42732 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class ZipExrCompression : ExrBaseDecompressor { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs similarity index 93% rename from src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs rename to src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 4fd5778cb..3dab1006e 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal abstract class ExrBaseCompression : IDisposable { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs similarity index 95% rename from src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs rename to src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 2af814eb1..12edcc104 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal abstract class ExrBaseDecompressor : ExrBaseCompression { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs similarity index 97% rename from src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs rename to src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs index 53fc2e2dc..a7b584ed4 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrCompressionType.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal enum ExrCompressionType { diff --git a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs similarity index 90% rename from src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs rename to src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 538231548..a293787fa 100644 --- a/src/ImageSharp/Formats/OpenExr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.OpenExr.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.OpenExr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal static class ExrDecompressorFactory { diff --git a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs b/src/ImageSharp/Formats/Exr/ExrAttribute.cs similarity index 92% rename from src/ImageSharp/Formats/OpenExr/ExrAttribute.cs rename to src/ImageSharp/Formats/Exr/ExrAttribute.cs index 0d6ca86cc..b4e95f1d4 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrAttribute.cs +++ b/src/ImageSharp/Formats/Exr/ExrAttribute.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] internal class ExrAttribute diff --git a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs b/src/ImageSharp/Formats/Exr/ExrBox2i.cs similarity index 91% rename from src/ImageSharp/Formats/OpenExr/ExrBox2i.cs rename to src/ImageSharp/Formats/Exr/ExrBox2i.cs index 1f5b0c0df..61da9b36d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/Exr/ExrBox2i.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] internal struct ExrBox2i diff --git a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs similarity index 94% rename from src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs rename to src/ImageSharp/Formats/Exr/ExrChannelInfo.cs index d39bc08a5..915b8201d 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs b/src/ImageSharp/Formats/Exr/ExrConfigurationModule.cs similarity index 93% rename from src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs rename to src/ImageSharp/Formats/Exr/ExrConfigurationModule.cs index c0322728a..a2f1b8c7a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConfigurationModule.cs +++ b/src/ImageSharp/Formats/Exr/ExrConfigurationModule.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. diff --git a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs b/src/ImageSharp/Formats/Exr/ExrConstants.cs similarity index 97% rename from src/ImageSharp/Formats/OpenExr/ExrConstants.cs rename to src/ImageSharp/Formats/Exr/ExrConstants.cs index 42ccf27fb..214dbd832 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrConstants.cs +++ b/src/ImageSharp/Formats/Exr/ExrConstants.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Defines constants relating to OpenExr images. diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs b/src/ImageSharp/Formats/Exr/ExrDecoder.cs similarity index 96% rename from src/ImageSharp/Formats/OpenExr/ExrDecoder.cs rename to src/ImageSharp/Formats/Exr/ExrDecoder.cs index 84d441eb7..11937eed4 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoder.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Image decoder for generating an image out of a OpenExr stream. diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs similarity index 99% rename from src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs rename to src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 6eaf56fde..732b4ff4a 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -6,13 +6,13 @@ using System.Buffers; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Text; -using SixLabors.ImageSharp.Formats.OpenExr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Performs the OpenExr decoding operation. diff --git a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs b/src/ImageSharp/Formats/Exr/ExrDecoderOptions.cs similarity index 87% rename from src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs rename to src/ImageSharp/Formats/Exr/ExrDecoderOptions.cs index c0b419b93..a8cda0b35 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrDecoderOptions.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Image decoder options for decoding OpenExr streams. diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs b/src/ImageSharp/Formats/Exr/ExrEncoder.cs similarity index 93% rename from src/ImageSharp/Formats/OpenExr/ExrEncoder.cs rename to src/ImageSharp/Formats/Exr/ExrEncoder.cs index 9960efd68..95913f2be 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoder.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Image encoder for writing an image to a stream in the OpenExr Format. diff --git a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs similarity index 99% rename from src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs rename to src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 740b71e12..98f43f6e2 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -6,12 +6,12 @@ using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Channels; -using SixLabors.ImageSharp.Formats.OpenExr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Image encoder for writing an image to a stream in the OpenExr format. diff --git a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs b/src/ImageSharp/Formats/Exr/ExrFormat.cs similarity index 94% rename from src/ImageSharp/Formats/OpenExr/ExrFormat.cs rename to src/ImageSharp/Formats/Exr/ExrFormat.cs index d367977fc..4415c15b0 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrFormat.cs +++ b/src/ImageSharp/Formats/Exr/ExrFormat.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Registers the image encoders, decoders and mime type detectors for the OpenExr format. diff --git a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs similarity index 93% rename from src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs rename to src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index 4cec4fbc5..29c30e793 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.OpenExr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Compression; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; internal class ExrHeaderAttributes { diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs b/src/ImageSharp/Formats/Exr/ExrImageDataType.cs similarity index 78% rename from src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs rename to src/ImageSharp/Formats/Exr/ExrImageDataType.cs index 4c3e19af8..92ca0f771 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/Exr/ExrImageDataType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; internal enum ExrImageDataType { diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs similarity index 95% rename from src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs rename to src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs index 2f97e7404..29548d52e 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs @@ -5,7 +5,7 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Formats.Qoi; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Detects OpenExr file headers. diff --git a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs b/src/ImageSharp/Formats/Exr/ExrImageType.cs similarity index 74% rename from src/ImageSharp/Formats/OpenExr/ExrImageType.cs rename to src/ImageSharp/Formats/Exr/ExrImageType.cs index a59aadbd6..bae5e2177 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrImageType.cs +++ b/src/ImageSharp/Formats/Exr/ExrImageType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; internal enum ExrImageType { diff --git a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs b/src/ImageSharp/Formats/Exr/ExrLineOrder.cs similarity index 78% rename from src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs rename to src/ImageSharp/Formats/Exr/ExrLineOrder.cs index dcf1c37c9..8b9a6d772 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrLineOrder.cs +++ b/src/ImageSharp/Formats/Exr/ExrLineOrder.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; internal enum ExrLineOrder : byte { diff --git a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs similarity index 96% rename from src/ImageSharp/Formats/OpenExr/ExrMetadata.cs rename to src/ImageSharp/Formats/Exr/ExrMetadata.cs index 9cb58e9e6..2e83826c9 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -4,7 +4,7 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Provides OpenExr specific metadata information for the image. diff --git a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs b/src/ImageSharp/Formats/Exr/ExrPixelType.cs similarity index 90% rename from src/ImageSharp/Formats/OpenExr/ExrPixelType.cs rename to src/ImageSharp/Formats/Exr/ExrPixelType.cs index 0dd49eaf8..7016063ec 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/Exr/ExrPixelType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// The different pixel formats for a OpenEXR image. diff --git a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs similarity index 95% rename from src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs rename to src/ImageSharp/Formats/Exr/ExrThrowHelper.cs index b609d0038..f668dcfef 100644 --- a/src/ImageSharp/Formats/OpenExr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Cold path optimizations for throwing exr format based exceptions. diff --git a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs similarity index 87% rename from src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs rename to src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs index ba2db62a0..0d90514e9 100644 --- a/src/ImageSharp/Formats/OpenExr/IExrEncoderOptions.cs +++ b/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.OpenExr; +namespace SixLabors.ImageSharp.Formats.Exr; /// /// Configuration options for use during OpenExr encoding. diff --git a/src/ImageSharp/Formats/OpenExr/README.md b/src/ImageSharp/Formats/Exr/README.md similarity index 100% rename from src/ImageSharp/Formats/OpenExr/README.md rename to src/ImageSharp/Formats/Exr/README.md diff --git a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs index ca4ad0e06..fa6eaa722 100644 --- a/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs @@ -13,7 +13,7 @@ using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Formats.Exr; namespace SixLabors.ImageSharp; diff --git a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs index 31a25fced..0dcbaed80 100644 --- a/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs +++ b/src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs @@ -14,7 +14,7 @@ using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Formats.Exr; namespace SixLabors.ImageSharp; diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 0391ab7c8..846dd42ca 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Exr; diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index 78ce845d3..a093ef4c5 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.OpenExr; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Exr; @@ -109,7 +109,7 @@ public class ImageExtensionsTest using (Image image = new(10, 10)) { - image.SaveAsTiff(memoryStream, new ExrEncoder()); + image.SaveAsExr(memoryStream, new ExrEncoder()); } memoryStream.Position = 0; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 9ced51608..8a40bd672 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -3,9 +3,9 @@ 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.OpenExr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; From b78402d5242a3024635c67bd82d43f238569284a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Mar 2026 19:18:30 +0100 Subject: [PATCH 059/240] Fix issue in ExrImageFormatDetector.cs returning wrong format --- src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs | 3 +-- tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs b/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs index 29548d52e..62663a4a7 100644 --- a/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Exr/ExrImageFormatDetector.cs @@ -3,7 +3,6 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; -using SixLabors.ImageSharp.Formats.Qoi; namespace SixLabors.ImageSharp.Formats.Exr; @@ -29,7 +28,7 @@ public sealed class ExrImageFormatDetector : IImageFormatDetector /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) { - format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null; + format = this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; return format != null; } } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs index a093ef4c5..cd23e1ccd 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -59,7 +59,7 @@ public class ImageExtensionsTest public async Task SaveAsExrAsync_Path_Encoder() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.tiff"); + string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.exr"); using (Image image = new(10, 10)) { From faf87fbe16066a583aac493cad7339666cefec08 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 14 Mar 2026 19:37:39 +0100 Subject: [PATCH 060/240] Use MagickReference decoder for exr decoding tests --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 13 ++++++++----- .../ReferenceCodecs/MagickReferenceDecoder.cs | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 846dd42ca..c90d4bdba 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests.Formats.Exr; @@ -10,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Exr; [ValidateDisposedMemoryAllocations] public class ExrDecoderTests { + private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed(TestImageProvider provider) @@ -17,7 +20,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -27,7 +30,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -37,7 +40,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -47,7 +50,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -57,6 +60,6 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 862d4b64d..a63818562 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -6,6 +6,7 @@ using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tiff; @@ -42,6 +43,8 @@ public class MagickReferenceDecoder : ImageDecoder public static MagickReferenceDecoder WebP { get; } = new(WebpFormat.Instance); + public static MagickReferenceDecoder Exr { get; } = new(ExrFormat.Instance); + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { ImageMetadata metadata = new(); @@ -118,6 +121,7 @@ public class MagickReferenceDecoder : ImageDecoder PixelType = metadata.GetDecodedPixelTypeInfo() }; } + private static void FromRgba32Bytes( Configuration configuration, Span rgbaBytes, From eaea042bc369dcaee361c00670f14df02cfe6ae9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Mar 2026 15:15:21 +0100 Subject: [PATCH 061/240] Fix issue assigning pixel value from HalfVector4 --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 732b4ff4a..7d74b5425 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -159,8 +159,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int x = 0; x < width; x++) { HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); - TPixel.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); } } From 490d4a0c2db8710ba24b9b1c353125c5eb3b005b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Mar 2026 16:56:04 +0100 Subject: [PATCH 062/240] Fix mistake in Identify --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 7d74b5425..533cff2fc 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -75,10 +75,19 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ExrCompressionType Compression { get; set; } + /// + /// Gets or sets the image data type, either RGB, RGBA or gray. + /// private ExrImageDataType ImageDataType { get; set; } + /// + /// Gets or sets the image type, either ScanLine or tiled. + /// private ExrImageType ImageType { get; set; } + /// + /// Gets or sets the header attributes. + /// private ExrHeaderAttributes HeaderAttributes { get; set; } /// @@ -134,7 +143,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); - TPixel color = default; for (uint y = 0; y < height; y += rowsPerBlock) { ulong rowOffset = this.ReadUnsignedLong(stream); @@ -368,7 +376,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore int bitsPerPixel = this.CalculateBitsPerPixel(); - return new ImageInfo(new Size((int)header.ScreenWindowWidth, (int)header.AspectRatio), this.metadata); + return new ImageInfo(new Size(header.DataWindow.XMax, header.DataWindow.YMax), this.metadata); } private int CalculateBitsPerPixel() From d12103995699ff176e66ff575df5cba9a2ae5436 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Mar 2026 16:56:24 +0100 Subject: [PATCH 063/240] Add test for Identify --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index c90d4bdba..0f039f7fb 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -13,6 +13,18 @@ public class ExrDecoderTests { private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; + [Theory] + [InlineData(TestImages.Exr.Uncompressed, 803, 1197)] + public void ExrDecoder_Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed(TestImageProvider provider) From 1b97d8c12ce786ca4f2ba297d0c505facb362ae4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Mar 2026 19:39:17 +0100 Subject: [PATCH 064/240] Smaller test files --- tests/Images/Input/Exr/Calliphora_b44.exr | 4 ++-- tests/Images/Input/Exr/Calliphora_rle.exr | 4 ++-- tests/Images/Input/Exr/Calliphora_uncompressed.exr | 4 ++-- tests/Images/Input/Exr/Calliphora_zip.exr | 4 ++-- tests/Images/Input/Exr/Calliphora_zips.exr | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Images/Input/Exr/Calliphora_b44.exr b/tests/Images/Input/Exr/Calliphora_b44.exr index ebe464170..ea008e070 100644 --- a/tests/Images/Input/Exr/Calliphora_b44.exr +++ b/tests/Images/Input/Exr/Calliphora_b44.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cde761051241cba1e7f8466d71602abdb130677de55602fe6afec4b287c75b9d -size 2533521 +oid sha256:7a6ac7fd879fc2dae646adaa14382aa10e5b8d02634af4668dd04bce09148151 +size 157973 diff --git a/tests/Images/Input/Exr/Calliphora_rle.exr b/tests/Images/Input/Exr/Calliphora_rle.exr index 20878f889..a956c9c40 100644 --- a/tests/Images/Input/Exr/Calliphora_rle.exr +++ b/tests/Images/Input/Exr/Calliphora_rle.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6302e72676f5574b8a9dde7e4385cff1c115e9833b630118328b88aea07e31d0 -size 4098454 +oid sha256:ccd25a952240a75d9c8b646be5001a2b006c053314e30cdf12e35ac865a31ae2 +size 292545 diff --git a/tests/Images/Input/Exr/Calliphora_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_uncompressed.exr index f0003a1f9..5dc90343d 100644 --- a/tests/Images/Input/Exr/Calliphora_uncompressed.exr +++ b/tests/Images/Input/Exr/Calliphora_uncompressed.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71613ff5972fa9e7b405ac944d159d1791ad229f5560da616438c9d718eafd24 -size 5798633 +oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 +size 362681 diff --git a/tests/Images/Input/Exr/Calliphora_zip.exr b/tests/Images/Input/Exr/Calliphora_zip.exr index 5d17150a4..82dc0dc39 100644 --- a/tests/Images/Input/Exr/Calliphora_zip.exr +++ b/tests/Images/Input/Exr/Calliphora_zip.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c78a7cf9fce29e725511acadf49f0f784914da5bef55b0e0c4e75ba09c3a75 -size 2652397 +oid sha256:a8b8e3b9a74179cb51a7a85031b3d177e109c20a56392804b10873273ff2163e +size 181871 diff --git a/tests/Images/Input/Exr/Calliphora_zips.exr b/tests/Images/Input/Exr/Calliphora_zips.exr index b4b297682..6556adf00 100644 --- a/tests/Images/Input/Exr/Calliphora_zips.exr +++ b/tests/Images/Input/Exr/Calliphora_zips.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e34a9b0bb7fd6eb24eef633dea737653812fd8ab5c5352f6e10523055823b1d -size 2918571 +oid sha256:9c6962a5b06c677648d5e22ff30ba8a28e7801fffb32259984e23bbaa97061b1 +size 220159 From e4c22d8f6725efd8b44492889f87a1b8ec1a587d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 15 Mar 2026 19:55:53 +0100 Subject: [PATCH 065/240] Add tests for decode RGB and gray exr files --- .../Formats/Exr/ExrDecoderTests.cs | 24 +++++++++++++++++-- tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Exr/Calliphora_gray.exr | 3 +++ tests/Images/Input/Exr/Calliphora_rgb.exr | 3 +++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Exr/Calliphora_gray.exr create mode 100644 tests/Images/Input/Exr/Calliphora_rgb.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 0f039f7fb..47e9b3271 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -14,7 +14,7 @@ public class ExrDecoderTests private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; [Theory] - [InlineData(TestImages.Exr.Uncompressed, 803, 1197)] + [InlineData(TestImages.Exr.Uncompressed, 199, 297)] public void ExrDecoder_Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) { TestFile testFile = TestFile.Create(imagePath); @@ -27,7 +27,27 @@ public class ExrDecoderTests [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] - public void ExrDecoder_CanDecode_Uncompressed(TestImageProvider provider) + public void ExrDecoder_CanDecode_Uncompressed_RGBA(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Rgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Rgb(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Gray, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 2429c05d2..06e35728e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1388,5 +1388,7 @@ public static class TestImages public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; public const string B44 = "Exr/Calliphora_b44.exr"; + public const string Rgb = "Exr/Calliphora_rgb.exr"; + public const string Gray = "Exr/Calliphora_gray.exr"; } } diff --git a/tests/Images/Input/Exr/Calliphora_gray.exr b/tests/Images/Input/Exr/Calliphora_gray.exr new file mode 100644 index 000000000..2aaeafbbe --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_gray.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a5daadcfd4ff0e45282d39d6c54f9a13651da3fd8841abda580e76661555470 +size 124245 diff --git a/tests/Images/Input/Exr/Calliphora_rgb.exr b/tests/Images/Input/Exr/Calliphora_rgb.exr new file mode 100644 index 000000000..5dc90343d --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_rgb.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 +size 362681 From 1abfe6966dd39a29bc40a2552604d49f96d0d460 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 16 Mar 2026 15:43:41 +0100 Subject: [PATCH 066/240] Add test image for decoding exr with float piyel type --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 12 +++++++++++- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/rgb_float32_uncompressed.exr | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Exr/rgb_float32_uncompressed.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 47e9b3271..9f1a532ac 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -27,7 +27,17 @@ public class ExrDecoderTests [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] - public void ExrDecoder_CanDecode_Uncompressed_RGBA(TestImageProvider provider) + public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Half(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.UncompressedFloatRgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Float(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 06e35728e..6d5302a9b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1390,5 +1390,6 @@ public static class TestImages public const string B44 = "Exr/Calliphora_b44.exr"; public const string Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; + public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; } } diff --git a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr new file mode 100644 index 000000000..8390e0f8e --- /dev/null +++ b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67b2d2f5fdacbe5cb2341fe230737fde041321224cebd65d5d12245a4f02d554 +size 388342 From fc40209727ada7d5ee638ea06b0753cf4a97d089 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 16 Mar 2026 19:30:09 +0100 Subject: [PATCH 067/240] Test case for pixel type uint --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 10 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 ++- tests/Images/Input/Exr/rgb_uint32_uncompressed.exr | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Exr/rgb_uint32_uncompressed.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 9f1a532ac..beca93448 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -45,6 +45,16 @@ public class ExrDecoderTests image.CompareToOriginal(provider, ReferenceDecoder); } + [Theory] + [WithFile(TestImages.Exr.UncompressedUintRgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + [Theory] [WithFile(TestImages.Exr.Rgb, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Rgb(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6d5302a9b..1b0c0c865 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1384,12 +1384,13 @@ public static class TestImages public static class Exr { public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; + public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; + public const string UncompressedUintRgb = "Exr/rgb_uint32_uncompressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; public const string B44 = "Exr/Calliphora_b44.exr"; public const string Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; - public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; } } diff --git a/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr b/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr new file mode 100644 index 000000000..616b676c3 --- /dev/null +++ b/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8de6e6f5b1c5acfc67d84e228c45f47ec16a1635f90fd484dfaeb89c28278f10 +size 101408 From 4b30c4ea10c80efbffe586b0668a4a6607d3b7ee Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 17 Mar 2026 11:02:22 +1000 Subject: [PATCH 068/240] Expand pixel blender API to support single color bulk operations --- .gitignore | 2 + .../DefaultPixelBlenders.Generated.cs | 14946 +++++++++++++--- .../DefaultPixelBlenders.Generated.tt | 84 + .../PixelFormats/PixelBlender{TPixel}.cs | 98 + src/ImageSharp/Primitives/Point.cs | 9 +- src/ImageSharp/Primitives/Rectangle.cs | 12 +- src/ImageSharp/Primitives/RectangleF.cs | 24 +- .../ImageSharp.Tests/Primitives/PointTests.cs | 30 +- .../Primitives/RectangleFTests.cs | 13 + .../Primitives/RectangleTests.cs | 13 + 10 files changed, 12263 insertions(+), 2968 deletions(-) diff --git a/.gitignore b/.gitignore index fadf36964..a8d2917be 100644 --- a/.gitignore +++ b/.gitignore @@ -227,3 +227,5 @@ artifacts/ #lfs hooks/** lfs/** + +.dotnet diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index f62d3c676..7cd9cc57a 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -81,8 +81,10 @@ internal static class DefaultPixelBlenders } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -90,65 +92,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "MultiplySrc" composition equation. - /// - public class MultiplySrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -157,34 +129,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -193,9 +175,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -207,10 +189,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -218,33 +199,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrc" composition equation. + /// A pixel blender that implements the "MultiplySrc" composition equation. /// - public class AddSrc : PixelBlender + public class MultiplySrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrc Instance { get; } = new AddSrc(); + public static MultiplySrc Instance { get; } = new MultiplySrc(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -264,7 +245,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -274,21 +255,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -296,65 +279,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "SubtractSrc" composition equation. - /// - public class SubtractSrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -363,34 +316,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -399,9 +362,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -413,10 +376,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -424,33 +386,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrc" composition equation. + /// A pixel blender that implements the "AddSrc" composition equation. /// - public class ScreenSrc : PixelBlender + public class AddSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); + public static AddSrc Instance { get; } = new AddSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -470,7 +432,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -480,21 +442,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -502,65 +466,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "DarkenSrc" composition equation. - /// - public class DarkenSrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -569,34 +503,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -605,9 +549,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -619,10 +563,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -630,33 +573,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrc" composition equation. + /// A pixel blender that implements the "SubtractSrc" composition equation. /// - public class LightenSrc : PixelBlender + public class SubtractSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrc Instance { get; } = new LightenSrc(); + public static SubtractSrc Instance { get; } = new SubtractSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -676,7 +619,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -686,21 +629,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -708,65 +653,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrc" composition equation. - /// - public class OverlaySrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -775,34 +690,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -811,9 +736,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -825,10 +750,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -836,33 +760,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrc" composition equation. + /// A pixel blender that implements the "ScreenSrc" composition equation. /// - public class HardLightSrc : PixelBlender + public class ScreenSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrc Instance { get; } = new HardLightSrc(); + public static ScreenSrc Instance { get; } = new ScreenSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -882,7 +806,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -892,21 +816,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -914,65 +840,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalSrcAtop" composition equation. - /// - public class NormalSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -981,34 +877,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -1017,9 +923,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -1031,10 +937,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -1042,33 +947,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// A pixel blender that implements the "DarkenSrc" composition equation. /// - public class MultiplySrcAtop : PixelBlender + public class DarkenSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + public static DarkenSrc Instance { get; } = new DarkenSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1088,7 +993,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1098,21 +1003,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1120,65 +1027,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "AddSrcAtop" composition equation. - /// - public class AddSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1187,34 +1064,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -1223,9 +1110,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -1237,10 +1124,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -1248,33 +1134,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// A pixel blender that implements the "LightenSrc" composition equation. /// - public class SubtractSrcAtop : PixelBlender + public class LightenSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + public static LightenSrc Instance { get; } = new LightenSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1294,7 +1180,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1304,21 +1190,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1326,65 +1214,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1393,34 +1251,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -1429,9 +1297,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -1443,10 +1311,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -1454,33 +1321,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// A pixel blender that implements the "OverlaySrc" composition equation. /// - public class DarkenSrcAtop : PixelBlender + public class OverlaySrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + public static OverlaySrc Instance { get; } = new OverlaySrc(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1500,7 +1367,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1510,21 +1377,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1532,65 +1401,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1599,34 +1438,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -1635,9 +1484,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -1649,10 +1498,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -1660,33 +1508,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// A pixel blender that implements the "HardLightSrc" composition equation. /// - public class OverlaySrcAtop : PixelBlender + public class HardLightSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + public static HardLightSrc Instance { get; } = new HardLightSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1706,7 +1554,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1716,21 +1564,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1738,65 +1588,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -1805,34 +1625,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -1841,9 +1671,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -1855,10 +1685,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -1866,33 +1695,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// A pixel blender that implements the "NormalSrcAtop" composition equation. /// - public class NormalSrcOver : PixelBlender + public class NormalSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1912,7 +1741,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1922,14 +1751,52 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); } } } @@ -1958,7 +1825,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1969,33 +1836,79 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } - } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. /// - public class MultiplySrcOver : PixelBlender + public class MultiplySrcAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); + public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2015,7 +1928,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2025,21 +1938,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2047,65 +1962,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "AddSrcOver" composition equation. - /// - public class AddSrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2114,34 +1999,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -2150,9 +2045,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -2164,10 +2059,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -2175,33 +2069,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// A pixel blender that implements the "AddSrcAtop" composition equation. /// - public class SubtractSrcOver : PixelBlender + public class AddSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); + public static AddSrcAtop Instance { get; } = new AddSrcAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2221,7 +2115,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2231,14 +2125,52 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); } } } @@ -2267,7 +2199,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2278,33 +2210,79 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. /// - public class ScreenSrcOver : PixelBlender + public class SubtractSrcAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2324,7 +2302,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2334,21 +2312,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2356,65 +2336,9946 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); } } } - } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public class ScreenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public class DarkenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public class LightenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public class OverlaySrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public class HardLightSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public class NormalSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public class MultiplySrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public class AddSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public class SubtractSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public class ScreenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public class DarkenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public class LightenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public class OverlaySrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public class HardLightSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public class NormalSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public class MultiplySrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public class AddSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public class SubtractSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public class ScreenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public class DarkenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public class LightenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public class OverlaySrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public class HardLightSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public class NormalSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public class MultiplySrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public class AddSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public class SubtractSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public class ScreenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public class DarkenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public class LightenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public class OverlaySrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public class HardLightSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public class NormalDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public class MultiplyDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public class AddDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public class SubtractDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public class ScreenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public class DarkenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public class LightenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public class OverlayDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public class HardLightDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public class NormalDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public class MultiplyDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public class AddDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public class SubtractDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public class ScreenDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestAtop" composition equation. + /// + public class DarkenDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// + public class LightenDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestAtop" composition equation. + /// + public class OverlayDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// + public class HardLightDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestOver" composition equation. + /// + public class NormalDestOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// A pixel blender that implements the "MultiplyDestOver" composition equation. /// - public class DarkenSrcOver : PixelBlender + public class MultiplyDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestOver" composition equation. + /// + public class AddDestOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2423,34 +12284,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -2459,9 +12330,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -2473,10 +12344,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -2484,33 +12354,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// A pixel blender that implements the "SubtractDestOver" composition equation. /// - public class LightenSrcOver : PixelBlender + public class SubtractDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2530,7 +12400,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2540,21 +12410,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2562,65 +12434,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2629,34 +12471,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -2665,9 +12517,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -2679,10 +12531,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -2690,33 +12541,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// A pixel blender that implements the "ScreenDestOver" composition equation. /// - public class HardLightSrcOver : PixelBlender + public class ScreenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2736,7 +12587,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2746,21 +12597,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2768,65 +12621,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2835,34 +12658,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -2871,9 +12704,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -2885,10 +12718,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -2896,33 +12728,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// A pixel blender that implements the "DarkenDestOver" composition equation. /// - public class MultiplySrcIn : PixelBlender + public class DarkenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2942,7 +12774,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2952,21 +12784,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -2974,65 +12808,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3041,34 +12845,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -3077,9 +12891,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -3091,10 +12905,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -3102,33 +12915,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// A pixel blender that implements the "LightenDestOver" composition equation. /// - public class SubtractSrcIn : PixelBlender + public class LightenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + public static LightenDestOver Instance { get; } = new LightenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3148,7 +12961,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3158,21 +12971,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3180,65 +12995,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3247,34 +13032,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -3283,9 +13078,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -3297,10 +13092,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -3308,33 +13102,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// A pixel blender that implements the "OverlayDestOver" composition equation. /// - public class DarkenSrcIn : PixelBlender + public class OverlayDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3354,7 +13148,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3364,21 +13158,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3386,65 +13182,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3453,34 +13219,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -3489,9 +13265,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -3503,10 +13279,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -3514,33 +13289,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// A pixel blender that implements the "HardLightDestOver" composition equation. /// - public class OverlaySrcIn : PixelBlender + public class HardLightDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3560,7 +13335,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3570,21 +13345,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3592,65 +13369,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); } } else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3659,34 +13406,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -3695,9 +13452,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -3709,10 +13466,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -3720,33 +13476,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// A pixel blender that implements the "NormalDestIn" composition equation. /// - public class NormalSrcOut : PixelBlender + public class NormalDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + public static NormalDestIn Instance { get; } = new NormalDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3766,7 +13522,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3776,21 +13532,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3798,65 +13556,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -3865,34 +13593,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -3901,9 +13639,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -3915,10 +13653,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -3926,33 +13663,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrcOut" composition equation. + /// A pixel blender that implements the "MultiplyDestIn" composition equation. /// - public class AddSrcOut : PixelBlender + public class MultiplyDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3972,7 +13709,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3982,21 +13719,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4004,65 +13743,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4071,34 +13780,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -4107,9 +13826,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -4121,10 +13840,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -4132,33 +13850,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// A pixel blender that implements the "AddDestIn" composition equation. /// - public class ScreenSrcOut : PixelBlender + public class AddDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + public static AddDestIn Instance { get; } = new AddDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4178,7 +13896,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4188,21 +13906,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4210,65 +13930,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4277,34 +13967,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -4313,9 +14013,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -4327,10 +14027,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -4338,33 +14037,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// A pixel blender that implements the "SubtractDestIn" composition equation. /// - public class LightenSrcOut : PixelBlender + public class SubtractDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4384,7 +14083,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4394,21 +14093,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4416,65 +14117,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4483,34 +14154,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -4519,9 +14200,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -4533,10 +14214,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -4544,33 +14224,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// A pixel blender that implements the "ScreenDestIn" composition equation. /// - public class HardLightSrcOut : PixelBlender + public class ScreenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4590,7 +14270,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4600,21 +14280,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4622,65 +14304,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4689,34 +14341,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -4725,9 +14387,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -4739,10 +14401,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -4750,33 +14411,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDest" composition equation. + /// A pixel blender that implements the "DarkenDestIn" composition equation. /// - public class MultiplyDest : PixelBlender + public class DarkenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4796,7 +14457,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4806,21 +14467,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4828,65 +14491,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); } } else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -4895,34 +14528,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -4931,9 +14574,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -4945,10 +14588,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -4956,33 +14598,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDest" composition equation. + /// A pixel blender that implements the "LightenDestIn" composition equation. /// - public class SubtractDest : PixelBlender + public class LightenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDest Instance { get; } = new SubtractDest(); + public static LightenDestIn Instance { get; } = new LightenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5002,7 +14644,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5012,21 +14654,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5034,65 +14678,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5101,34 +14715,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -5137,9 +14761,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -5151,10 +14775,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -5162,33 +14785,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDest" composition equation. + /// A pixel blender that implements the "OverlayDestIn" composition equation. /// - public class DarkenDest : PixelBlender + public class OverlayDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDest Instance { get; } = new DarkenDest(); + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5208,7 +14831,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5218,21 +14841,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5240,65 +14865,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5307,34 +14902,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -5343,9 +14948,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -5357,10 +14962,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -5368,33 +14972,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDest" composition equation. + /// A pixel blender that implements the "HardLightDestIn" composition equation. /// - public class OverlayDest : PixelBlender + public class HardLightDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDest Instance { get; } = new OverlayDest(); + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5414,7 +15018,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5424,21 +15028,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5446,65 +15052,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5513,34 +15089,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -5549,9 +15135,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -5563,10 +15149,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -5574,33 +15159,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// A pixel blender that implements the "NormalDestOut" composition equation. /// - public class NormalDestAtop : PixelBlender + public class NormalDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + public static NormalDestOut Instance { get; } = new NormalDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5620,7 +15205,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5630,21 +15215,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5652,65 +15239,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5719,34 +15276,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -5755,9 +15322,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -5769,10 +15336,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -5780,33 +15346,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddDestAtop" composition equation. + /// A pixel blender that implements the "MultiplyDestOut" composition equation. /// - public class AddDestAtop : PixelBlender + public class MultiplyDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5826,7 +15392,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5836,21 +15402,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5858,65 +15426,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -5925,34 +15463,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -5961,9 +15509,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -5975,10 +15523,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -5986,33 +15533,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// A pixel blender that implements the "AddDestOut" composition equation. /// - public class ScreenDestAtop : PixelBlender + public class AddDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + public static AddDestOut Instance { get; } = new AddDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6032,7 +15579,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6042,21 +15589,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6064,65 +15613,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); } } else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6131,34 +15650,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -6167,9 +15696,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -6181,10 +15710,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -6192,33 +15720,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// A pixel blender that implements the "SubtractDestOut" composition equation. /// - public class LightenDestAtop : PixelBlender + public class SubtractDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6238,7 +15766,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6248,21 +15776,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6270,65 +15800,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6337,34 +15837,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -6373,9 +15883,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -6387,10 +15897,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -6398,33 +15907,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// A pixel blender that implements the "ScreenDestOut" composition equation. /// - public class HardLightDestAtop : PixelBlender + public class ScreenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6444,7 +15953,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6454,21 +15963,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6476,65 +15987,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6543,34 +16024,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -6579,9 +16070,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -6593,10 +16084,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -6604,33 +16094,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// A pixel blender that implements the "DarkenDestOut" composition equation. /// - public class MultiplyDestOver : PixelBlender + public class DarkenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6650,7 +16140,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6660,21 +16150,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6682,65 +16174,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6749,34 +16211,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -6785,9 +16257,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -6799,10 +16271,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -6810,33 +16281,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// A pixel blender that implements the "LightenDestOut" composition equation. /// - public class SubtractDestOver : PixelBlender + public class LightenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + public static LightenDestOut Instance { get; } = new LightenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6856,7 +16327,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6866,21 +16337,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6888,65 +16361,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenDestOver" composition equation. - /// - public class ScreenDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -6955,34 +16398,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -6991,9 +16444,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -7005,10 +16458,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -7016,33 +16468,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// A pixel blender that implements the "OverlayDestOut" composition equation. /// - public class DarkenDestOver : PixelBlender + public class OverlayDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7062,7 +16514,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7072,21 +16524,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7094,65 +16548,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenDestOver" composition equation. - /// - public class LightenDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7161,34 +16585,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -7197,9 +16631,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -7211,10 +16645,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -7222,33 +16655,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// A pixel blender that implements the "HardLightDestOut" composition equation. /// - public class OverlayDestOver : PixelBlender + public class HardLightDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7268,7 +16701,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7278,21 +16711,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7300,65 +16735,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); } } else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOver" composition equation. - /// - public class HardLightDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7367,34 +16772,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -7403,9 +16818,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -7417,10 +16832,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -7428,33 +16842,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalDestIn" composition equation. + /// A pixel blender that implements the "NormalClear" composition equation. /// - public class NormalDestIn : PixelBlender + public class NormalClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); + public static NormalClear Instance { get; } = new NormalClear(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7474,7 +16888,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7484,21 +16898,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7506,65 +16922,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "MultiplyDestIn" composition equation. - /// - public class MultiplyDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7573,34 +16959,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -7609,9 +17005,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -7623,10 +17019,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -7634,33 +17029,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddDestIn" composition equation. + /// A pixel blender that implements the "MultiplyClear" composition equation. /// - public class AddDestIn : PixelBlender + public class MultiplyClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddDestIn Instance { get; } = new AddDestIn(); + public static MultiplyClear Instance { get; } = new MultiplyClear(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7680,7 +17075,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7690,21 +17085,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7712,65 +17109,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "SubtractDestIn" composition equation. - /// - public class SubtractDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7779,34 +17146,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -7815,9 +17192,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -7829,10 +17206,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -7840,33 +17216,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// A pixel blender that implements the "AddClear" composition equation. /// - public class ScreenDestIn : PixelBlender + public class AddClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + public static AddClear Instance { get; } = new AddClear(); /// 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.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7886,7 +17262,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7896,21 +17272,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7918,65 +17296,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestIn" composition equation. - /// - public class DarkenDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -7985,34 +17333,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -8021,9 +17379,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -8035,10 +17393,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -8046,33 +17403,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenDestIn" composition equation. + /// A pixel blender that implements the "SubtractClear" composition equation. /// - public class LightenDestIn : PixelBlender + public class SubtractClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); + public static SubtractClear Instance { get; } = new SubtractClear(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8092,7 +17449,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8102,21 +17459,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8124,65 +17483,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlayDestIn" composition equation. - /// - public class OverlayDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8191,34 +17520,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -8227,9 +17566,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -8241,10 +17580,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -8252,33 +17590,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// A pixel blender that implements the "ScreenClear" composition equation. /// - public class HardLightDestIn : PixelBlender + public class ScreenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + public static ScreenClear Instance { get; } = new ScreenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8298,7 +17636,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8308,21 +17646,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8330,65 +17670,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalDestOut" composition equation. - /// - public class NormalDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8397,34 +17707,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -8433,9 +17753,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -8447,10 +17767,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -8458,33 +17777,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// A pixel blender that implements the "DarkenClear" composition equation. /// - public class MultiplyDestOut : PixelBlender + public class DarkenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + public static DarkenClear Instance { get; } = new DarkenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8504,7 +17823,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8514,21 +17833,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8536,65 +17857,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); } } else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOut" composition equation. - /// - public class AddDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8603,34 +17894,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -8639,9 +17940,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -8653,10 +17954,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -8664,33 +17964,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// A pixel blender that implements the "LightenClear" composition equation. /// - public class SubtractDestOut : PixelBlender + public class LightenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + public static LightenClear Instance { get; } = new LightenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8710,7 +18010,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8720,21 +18020,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8742,65 +18044,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenDestOut" composition equation. - /// - public class ScreenDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8809,34 +18081,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -8845,9 +18127,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -8859,10 +18141,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -8870,33 +18151,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// A pixel blender that implements the "OverlayClear" composition equation. /// - public class DarkenDestOut : PixelBlender + public class OverlayClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + public static OverlayClear Instance { get; } = new OverlayClear(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8916,7 +18197,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8926,21 +18207,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -8948,65 +18231,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenDestOut" composition equation. - /// - public class LightenDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9015,34 +18268,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -9051,9 +18314,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -9065,10 +18328,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -9076,33 +18338,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// A pixel blender that implements the "HardLightClear" composition equation. /// - public class OverlayDestOut : PixelBlender + public class HardLightClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + public static HardLightClear Instance { get; } = new HardLightClear(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9122,7 +18384,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9132,21 +18394,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9154,65 +18418,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOut" composition equation. - /// - public class HardLightDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); + } + } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9221,34 +18455,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -9257,9 +18501,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -9271,10 +18515,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -9282,33 +18525,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalClear" composition equation. + /// A pixel blender that implements the "NormalXor" composition equation. /// - public class NormalClear : PixelBlender + public class NormalXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalClear Instance { get; } = new NormalClear(); + public static NormalXor Instance { get; } = new NormalXor(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9328,7 +18571,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9338,21 +18581,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9360,65 +18605,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "MultiplyClear" composition equation. - /// - public class MultiplyClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9427,34 +18642,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -9463,9 +18688,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -9477,10 +18702,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -9488,33 +18712,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddClear" composition equation. + /// A pixel blender that implements the "MultiplyXor" composition equation. /// - public class AddClear : PixelBlender + public class MultiplyXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddClear Instance { get; } = new AddClear(); + public static MultiplyXor Instance { get; } = new MultiplyXor(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9534,7 +18758,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9544,21 +18768,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9566,65 +18792,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "SubtractClear" composition equation. - /// - public class SubtractClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9633,34 +18829,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -9669,9 +18875,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -9683,10 +18889,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -9694,33 +18899,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenClear" composition equation. + /// A pixel blender that implements the "AddXor" composition equation. /// - public class ScreenClear : PixelBlender + public class AddXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenClear Instance { get; } = new ScreenClear(); + public static AddXor Instance { get; } = new AddXor(); /// 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.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9740,7 +18945,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9750,21 +18955,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9772,65 +18979,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9839,34 +19016,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -9875,9 +19062,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -9889,10 +19076,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -9900,33 +19086,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenClear" composition equation. + /// A pixel blender that implements the "SubtractXor" composition equation. /// - public class LightenClear : PixelBlender + public class SubtractXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenClear Instance { get; } = new LightenClear(); + public static SubtractXor Instance { get; } = new SubtractXor(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9946,7 +19132,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9956,21 +19142,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -9978,65 +19166,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "OverlayClear" composition equation. - /// - public class OverlayClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10045,34 +19203,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -10081,9 +19249,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -10095,10 +19263,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -10106,33 +19273,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightClear" composition equation. + /// A pixel blender that implements the "ScreenXor" composition equation. /// - public class HardLightClear : PixelBlender + public class ScreenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightClear Instance { get; } = new HardLightClear(); + public static ScreenXor Instance { get; } = new ScreenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10152,7 +19319,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10162,21 +19329,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10184,65 +19353,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "NormalXor" composition equation. - /// - public class NormalXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10251,34 +19390,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -10287,9 +19436,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -10301,10 +19450,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -10312,33 +19460,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyXor" composition equation. + /// A pixel blender that implements the "DarkenXor" composition equation. /// - public class MultiplyXor : PixelBlender + public class DarkenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); + public static DarkenXor Instance { get; } = new DarkenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10358,7 +19506,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10368,21 +19516,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10390,65 +19540,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "AddXor" composition equation. - /// - public class AddXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10457,34 +19577,44 @@ internal static class DefaultPixelBlenders 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); + 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); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -10493,9 +19623,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -10507,10 +19637,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -10518,33 +19647,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractXor" composition equation. + /// A pixel blender that implements the "LightenXor" composition equation. /// - public class SubtractXor : PixelBlender + public class LightenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractXor Instance { get; } = new SubtractXor(); + public static LightenXor Instance { get; } = new LightenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10564,7 +19693,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10574,21 +19703,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10596,65 +19727,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "ScreenXor" composition equation. - /// - public class ScreenXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10663,34 +19764,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -10699,9 +19810,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -10713,10 +19824,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -10724,33 +19834,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenXor" composition equation. + /// A pixel blender that implements the "OverlayXor" composition equation. /// - public class DarkenXor : PixelBlender + public class OverlayXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenXor Instance { get; } = new DarkenXor(); + public static OverlayXor Instance { get; } = new OverlayXor(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10770,7 +19880,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10780,21 +19890,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10802,65 +19914,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "LightenXor" composition equation. - /// - public class LightenXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -10869,34 +19951,44 @@ internal static class DefaultPixelBlenders 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); + ref float amountBase = ref MemoryMarshal.GetReference(amount); + + Vector256 vOne = Vector256.Create(1F); while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next amount values + // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -10905,9 +19997,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -10919,10 +20011,9 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); } @@ -10930,33 +20021,33 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayXor" composition equation. + /// A pixel blender that implements the "HardLightXor" composition equation. /// - public class OverlayXor : PixelBlender + public class HardLightXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayXor Instance { get; } = new OverlayXor(); + public static HardLightXor Instance { get; } = new HardLightXor(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10976,7 +20067,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10986,21 +20077,23 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { + amount = Numerics.Clamp(amount, 0, 1); + if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -11008,65 +20101,35 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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); + 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)) { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp 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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); } } } - } - - /// - /// A pixel blender that implements the "HardLightXor" composition equation. - /// - public class HardLightXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 @@ -11075,34 +20138,44 @@ internal static class DefaultPixelBlenders 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); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { if (Avx2.IsSupported && destination.Length >= 2) { @@ -11111,9 +20184,9 @@ internal static class DefaultPixelBlenders ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 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)) @@ -11128,7 +20201,6 @@ internal static class DefaultPixelBlenders destinationBase = PorterDuffFunctions.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); } @@ -11136,14 +20208,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 5b71bb0a6..3b885826b 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -123,6 +123,44 @@ var blenders = new []{ } } + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.<#=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] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + } + } + } + /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { @@ -169,6 +207,52 @@ var blenders = new []{ } } } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } } <# diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 4ed617438..bad9ae01c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -64,6 +64,39 @@ public abstract class PixelBlender PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); } + /// + /// Blends a row against a constant source color. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + Span destinationVectors = buffer.Slice(0, maxLength); + Span backgroundVectors = buffer.Slice(maxLength, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + } + /// /// Blends 2 rows together /// @@ -121,6 +154,39 @@ public abstract class PixelBlender PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); } + /// + /// Blends a row against a constant source color. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + Span destinationVectors = buffer.Slice(0, maxLength); + Span backgroundVectors = buffer.Slice(maxLength, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + } + /// /// Blend 2 rows together. /// @@ -137,6 +203,22 @@ public abstract class PixelBlender ReadOnlySpan source, float amount); + /// + /// Blend a row against a constant source color. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount); + /// /// Blend 2 rows together. /// @@ -152,4 +234,20 @@ public abstract class PixelBlender ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount); + + /// + /// Blend a row against a constant source color. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount); } diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs index 766085547..99193e3bb 100644 --- a/src/ImageSharp/Primitives/Point.cs +++ b/src/ImageSharp/Primitives/Point.cs @@ -233,7 +233,8 @@ public struct Point : IEquatable /// The transformation matrix used. /// The transformed . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); + public static PointF Transform(Point point, Matrix3x2 matrix) + => Vector2.Transform(new Vector2(point.X, point.Y), matrix); /// /// Transforms a point by a specified 4x4 matrix, applying a projective transform @@ -241,10 +242,10 @@ public struct Point : IEquatable /// /// The point to transform. /// The transformation matrix used. - /// The transformed . + /// The transformed . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Transform(Point point, Matrix4x4 matrix) - => Round(TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix)); + public static PointF Transform(Point point, Matrix4x4 matrix) + => TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix); /// /// Deconstructs this point into two integers. diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index 649496441..26a94837f 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -260,11 +260,7 @@ public struct Rectangle : IEquatable /// The transformation matrix. /// A transformed rectangle. public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) - { - PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = Point.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } + => RectangleF.Transform(rectangle, matrix); /// /// Transforms a rectangle by the given 4x4 matrix, applying a projective transform @@ -274,11 +270,7 @@ public struct Rectangle : IEquatable /// The transformation matrix. /// A transformed rectangle. public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix) - { - PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } + => RectangleF.Transform(rectangle, matrix); /// /// Converts a to a by performing a truncate operation on all the coordinates. diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs index a66e3fcad..3f41c2d07 100644 --- a/src/ImageSharp/Primitives/RectangleF.cs +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -236,9 +236,17 @@ public struct RectangleF : IEquatable /// A transformed . public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) { - PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); PointF topLeft = PointF.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix); + PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix); + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + + float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X)); + float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y)); + float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X)); + float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y)); + + return FromLTRB(left, top, right, bottom); } /// @@ -250,9 +258,17 @@ public struct RectangleF : IEquatable /// A transformed . public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix) { - PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); PointF topLeft = PointF.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix); + PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix); + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + + float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X)); + float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y)); + float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X)); + float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y)); + + return FromLTRB(left, top, right, bottom); } /// diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs index 22c18a147..d2192f53b 100644 --- a/tests/ImageSharp.Tests/Primitives/PointTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -159,9 +159,10 @@ public class PointTests Point p = new(13, 17); Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); - Point pout = Point.Transform(p, matrix); + PointF pout = Point.Transform(p, matrix); - Assert.Equal(new Point(-3, 21), pout); + Assert.Equal(-2.828427F, pout.X, 4); + Assert.Equal(21.213203F, pout.Y, 4); } [Fact] @@ -170,8 +171,9 @@ public class PointTests Point p = new(13, 17); Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); - Point pout = Point.Transform(p, matrix); - Assert.Equal(new Point(30, 30), pout); + PointF pout = Point.Transform(p, matrix); + Assert.Equal(30F, pout.X, 4); + Assert.Equal(30F, pout.Y, 4); } [Fact] @@ -181,8 +183,8 @@ public class PointTests Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); Matrix4x4 m4 = new(m3); - Point r3 = Point.Transform(p, m3); - Point r4 = Point.Transform(p, m4); + PointF r3 = Point.Transform(p, m3); + PointF r4 = Point.Transform(p, m4); Assert.Equal(r3, r4); } @@ -191,9 +193,9 @@ public class PointTests public void TransformMatrix4x4_Identity() { Point p = new(42, -17); - Point result = Point.Transform(p, Matrix4x4.Identity); + PointF result = Point.Transform(p, Matrix4x4.Identity); - Assert.Equal(p, result); + Assert.Equal((PointF)p, result); } [Fact] @@ -201,9 +203,10 @@ public class PointTests { Point p = new(10, 20); Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); - Point result = Point.Transform(p, m); + PointF result = Point.Transform(p, m); - Assert.Equal(new Point(15, 17), result); + Assert.Equal(15F, result.X, 4); + Assert.Equal(17F, result.Y, 4); } [Fact] @@ -213,10 +216,11 @@ public class PointTests Matrix4x4 m = Matrix4x4.Identity; m.M14 = 0.005F; - Point result = Point.Transform(p, m); + PointF result = Point.Transform(p, m); - // W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) => rounded - Assert.Equal(Point.Round(new PointF(100F / 1.5F, 50F / 1.5F)), result); + // W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) + Assert.Equal(100F / 1.5F, result.X, 4); + Assert.Equal(50F / 1.5F, result.Y, 4); } [Theory] diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs index e3a13618b..9a6ff0957 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -286,6 +286,19 @@ public class RectangleFTests Assert.Equal(new RectangleF(20, 60, 200, 150), result); } + [Fact] + public void TransformMatrix4x4_RotationReturnsBoundingBox() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(-70F, result.X, 4); + Assert.Equal(10F, result.Y, 4); + Assert.Equal(50F, result.Width, 4); + Assert.Equal(100F, result.Height, 4); + } + [Fact] public void ToStringTest() { diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs index 104bce754..a8ce99078 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -327,6 +327,19 @@ public class RectangleTests Assert.Equal(new RectangleF(15, 17, 100, 50), result); } + [Fact] + public void TransformMatrix4x4_RotationReturnsBoundingBox() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F); + RectangleF result = Rectangle.Transform(rect, m); + + Assert.Equal(-70F, result.X, 4); + Assert.Equal(10F, result.Y, 4); + Assert.Equal(50F, result.Width, 4); + Assert.Equal(100F, result.Height, 4); + } + [Fact] public void ToStringTest() { From a0eaefbae95158d28b0eeea7d84f81e891516212 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Mar 2026 09:50:16 +0100 Subject: [PATCH 069/240] Try fix decode unsigned int pixel data --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 15 +++++++++++---- .../Images/Input/Exr/rgb_uint32_uncompressed.exr | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 533cff2fc..b823f452e 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -4,7 +4,9 @@ using System.Buffers; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; using System.Text; using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.IO; @@ -19,6 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Exr; /// internal sealed class ExrDecoderCore : ImageDecoderCore { + private const float Scale32Bit = 1f / 0xFFFFFFFF; + private static readonly Vector4 Scale32BitVector = Vector128.Create(Scale32Bit, Scale32Bit, Scale32Bit, 1f).AsVector4(); + /// /// Reusable buffer. /// @@ -196,7 +201,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); - TPixel color = default; for (uint y = 0; y < height; y += rowsPerBlock) { ulong rowOffset = this.ReadUnsignedLong(stream); @@ -222,9 +226,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int x = 0; x < width; x++) { - Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - TPixel.FromVector4(pixelValue.ToVector4()); - pixelRow[x] = color; + pixelRow[x] = ColorScaleTo32Bit(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); } } } @@ -799,4 +801,9 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); } + + [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); } diff --git a/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr b/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr index 616b676c3..9a652749f 100644 --- a/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr +++ b/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8de6e6f5b1c5acfc67d84e228c45f47ec16a1635f90fd484dfaeb89c28278f10 -size 101408 +oid sha256:487f4641908b0c5c34e7cc3be439eff31e5e8d2fd03a9d17d39dc3dc5e55874d +size 243558 From b79ba2158d44e1bc9176e671519ce4c8cb60eaa9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Mar 2026 10:31:50 +0100 Subject: [PATCH 070/240] Fix not using offset when accessing decompressedPixelData --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index b823f452e..c19436d52 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -219,7 +219,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData, redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } stream.Position = nextRowOffsetPosition; From 51f3ab2526b1691a67a6a8515cc8d81972559df4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 17 Mar 2026 13:36:29 +0100 Subject: [PATCH 071/240] Fix test image for float32: it is now actually uncompressed --- tests/Images/Input/Exr/rgb_float32_uncompressed.exr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr index 8390e0f8e..489758bb0 100644 --- a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr +++ b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67b2d2f5fdacbe5cb2341fe230737fde041321224cebd65d5d12245a4f02d554 -size 388342 +oid sha256:5ab8b71824ca384fc2cde1f74a6f34c38169fa328ccc67f17d08333e9de42f55 +size 243558 From 3b7aa4d55469e29c6dacaddf09e6bf9aa25b723d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2026 15:30:04 +0100 Subject: [PATCH 072/240] Move constants to own folder --- src/ImageSharp/Formats/Exr/{ => Constants}/ExrImageDataType.cs | 2 +- src/ImageSharp/Formats/Exr/{ => Constants}/ExrImageType.cs | 2 +- src/ImageSharp/Formats/Exr/{ => Constants}/ExrLineOrder.cs | 2 +- src/ImageSharp/Formats/Exr/{ => Constants}/ExrPixelType.cs | 2 +- src/ImageSharp/Formats/Exr/ExrChannelInfo.cs | 1 + src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 1 + src/ImageSharp/Formats/Exr/ExrEncoder.cs | 2 ++ src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 1 + src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs | 1 + src/ImageSharp/Formats/Exr/ExrMetadata.cs | 1 + src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs | 2 ++ 11 files changed, 13 insertions(+), 4 deletions(-) rename src/ImageSharp/Formats/Exr/{ => Constants}/ExrImageDataType.cs (76%) rename src/ImageSharp/Formats/Exr/{ => Constants}/ExrImageType.cs (72%) rename src/ImageSharp/Formats/Exr/{ => Constants}/ExrLineOrder.cs (76%) rename src/ImageSharp/Formats/Exr/{ => Constants}/ExrPixelType.cs (89%) diff --git a/src/ImageSharp/Formats/Exr/ExrImageDataType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs similarity index 76% rename from src/ImageSharp/Formats/Exr/ExrImageDataType.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs index 92ca0f771..d63a8e219 100644 --- a/src/ImageSharp/Formats/Exr/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; internal enum ExrImageDataType { diff --git a/src/ImageSharp/Formats/Exr/ExrImageType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs similarity index 72% rename from src/ImageSharp/Formats/Exr/ExrImageType.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs index bae5e2177..beeabe35e 100644 --- a/src/ImageSharp/Formats/Exr/ExrImageType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; internal enum ExrImageType { diff --git a/src/ImageSharp/Formats/Exr/ExrLineOrder.cs b/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs similarity index 76% rename from src/ImageSharp/Formats/Exr/ExrLineOrder.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs index 8b9a6d772..56573472c 100644 --- a/src/ImageSharp/Formats/Exr/ExrLineOrder.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; internal enum ExrLineOrder : byte { diff --git a/src/ImageSharp/Formats/Exr/ExrPixelType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrPixelType.cs similarity index 89% rename from src/ImageSharp/Formats/Exr/ExrPixelType.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrPixelType.cs index 7016063ec..cddc43d59 100644 --- a/src/ImageSharp/Formats/Exr/ExrPixelType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrPixelType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; /// /// The different pixel formats for a OpenEXR image. diff --git a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs index 915b8201d..d4f5825b9 100644 --- a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index c19436d52..38e9c3ec9 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Text; using SixLabors.ImageSharp.Formats.Exr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; diff --git a/src/ImageSharp/Formats/Exr/ExrEncoder.cs b/src/ImageSharp/Formats/Exr/ExrEncoder.cs index 95913f2be..15e10ba2f 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoder.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Exr.Constants; + namespace SixLabors.ImageSharp.Formats.Exr; /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 98f43f6e2..400915d22 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Channels; using SixLabors.ImageSharp.Formats.Exr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index 29c30e793..35611bb6e 100644 --- a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Exr.Compression; +using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 2e83826c9..552d2d113 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Exr; diff --git a/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs index 0d90514e9..2ccf26ec1 100644 --- a/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs +++ b/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Exr.Constants; + namespace SixLabors.ImageSharp.Formats.Exr; /// From 137ebbba0654b91000cbfaa665467591c1f9df99 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 19 Mar 2026 19:02:47 +0100 Subject: [PATCH 073/240] Add Exr compressor factory --- .../Compression/Compressors/NoCompressor.cs | 8 ++++ .../Compression/Compressors/ZipCompressor.cs | 47 +++++++++++++++++++ .../Decompressors/B44Compression.cs | 4 +- .../Decompressors/NoneExrCompression.cs | 6 +-- .../Decompressors/RunLengthCompression.cs | 6 +-- .../Decompressors/ZipExrCompression.cs | 6 +-- .../Exr/Compression/ExrBaseCompression.cs | 23 +++++++-- .../Exr/Compression/ExrBaseDecompressor.cs | 4 +- .../Exr/Compression/ExrCompressorFactory.cs | 26 ++++++++++ .../Exr/Compression/ExrDecompressorFactory.cs | 23 ++++----- .../ExrCompression.cs} | 7 ++- .../Formats/Exr/ExrBaseCompressor.cs | 43 +++++++++++++++++ src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 26 +++++----- src/ImageSharp/Formats/Exr/ExrEncoder.cs | 5 ++ src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 5 +- .../Formats/Exr/ExrHeaderAttributes.cs | 5 +- src/ImageSharp/Formats/Exr/ExrThrowHelper.cs | 11 +++++ 17 files changed, 206 insertions(+), 49 deletions(-) create mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs create mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs create mode 100644 src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs rename src/ImageSharp/Formats/Exr/{Compression/ExrCompressionType.cs => Constants/ExrCompression.cs} (91%) create mode 100644 src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs new file mode 100644 index 000000000..9a9bc8fff --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; + +internal class NoCompressor +{ +} diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs new file mode 100644 index 000000000..d205c2f1a --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; + +internal class ZipCompressor : ExrBaseCompressor +{ + private readonly DeflateCompressionLevel compressionLevel; + + private readonly MemoryStream memoryStream = new(); + + public ZipCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock) + => this.compressionLevel = compressionLevel; + + /// + public override ExrCompression Method => ExrCompression.Zip; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) + { + this.memoryStream.Seek(0, SeekOrigin.Begin); + using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) + { + stream.Write(rows); + stream.Flush(); + } + + int size = (int)this.memoryStream.Position; + byte[] buffer = this.memoryStream.GetBuffer(); + this.Output.Write(buffer, 0, size); + } + + /// + protected override void Dispose(bool disposing) + { + } +} diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs index b8cb46814..d0f9eb6ef 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs @@ -24,8 +24,8 @@ internal class B44Compression : ExrBaseDecompressor private IMemoryOwner tmpBuffer; - public B44Compression(MemoryAllocator allocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) - : base(allocator, uncompressedBytes) + public B44Compression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) + : base(allocator, bytesPerBlock) { this.width = width; this.height = height; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 6b36dffa2..75bf67cb2 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -8,13 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class NoneExrCompression : ExrBaseDecompressor { - public NoneExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) { } public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) - => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.UncompressedBytes)); + => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs index f1c7bb759..15538a813 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs @@ -19,7 +19,7 @@ internal class RunLengthCompression : ExrBaseDecompressor public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); - int maxLength = (int)this.UncompressedBytes; + int maxLength = (int)this.BytesPerBlock; int offset = 0; while (compressedBytes > 0) { @@ -63,8 +63,8 @@ internal class RunLengthCompression : ExrBaseDecompressor } } - Reconstruct(uncompressed, this.UncompressedBytes); - Interleave(uncompressed, this.UncompressedBytes, buffer); + Reconstruct(uncompressed, this.BytesPerBlock); + Interleave(uncompressed, this.BytesPerBlock, buffer); } private static byte ReadNextByte(BufferedReadStream stream) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 4d9b42732..95d4c0313 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -13,8 +13,8 @@ internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - public ZipExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { @@ -28,7 +28,7 @@ internal class ZipExrCompression : ExrBaseDecompressor int left = (int)(compressedBytes - (stream.Position - pos)); return left > 0 ? left : 0; }); - inflateStream.AllocateNewBytes((int)this.UncompressedBytes, true); + inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); DeflateStream dataStream = inflateStream.CompressedStream!; int totalRead = 0; diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 3dab1006e..e510135c2 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -9,10 +9,10 @@ internal abstract class ExrBaseCompression : IDisposable { private bool isDisposed; - protected ExrBaseCompression(MemoryAllocator allocator, uint bytePerRow) + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock) { this.Allocator = allocator; - this.UncompressedBytes = bytePerRow; + this.BytesPerBlock = bytesPerBlock; } /// @@ -21,9 +21,24 @@ internal abstract class ExrBaseCompression : IDisposable protected MemoryAllocator Allocator { get; } /// - /// Gets the uncompressed bytes. + /// Gets the bits per pixel. /// - public uint UncompressedBytes { get; } + public int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow { get; } + + /// + /// Gets the uncompressed bytes per block. + /// + public uint BytesPerBlock { get; } + + /// + /// Gets the image width. + /// + public int Width { get; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 12edcc104..0dd7d0110 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -8,8 +8,8 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal abstract class ExrBaseDecompressor : ExrBaseCompression { - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytePerRow) - : base(allocator, bytePerRow) + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs new file mode 100644 index 000000000..fc403be3a --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression; + +internal static class ExrCompressorFactory +{ + public static ExrBaseCompressor Create( + ExrCompression method, + Stream output, + MemoryAllocator allocator, + int width, + DeflateCompressionLevel compressionLevel) + { + switch (method) + { + default: + throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); + } + } +} diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index a293787fa..6764a2a13 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -2,26 +2,27 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompressionType method, MemoryAllocator memoryAllocator, uint uncompressedBytes, int width, int height, uint rowsPerBlock, int channelCount) + public static ExrBaseDecompressor Create(ExrCompression method, MemoryAllocator memoryAllocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) { switch (method) { - case ExrCompressionType.None: - return new NoneExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zips: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.Zip: - return new ZipExrCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, uncompressedBytes); - case ExrCompressionType.B44: - return new B44Compression(memoryAllocator, uncompressedBytes, width, height, rowsPerBlock, channelCount); + case ExrCompression.None: + return new NoneExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.Zips: + return new ZipExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.Zip: + return new ZipExrCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.RunLengthEncoded: + return new RunLengthCompression(memoryAllocator, bytesPerBlock); + case ExrCompression.B44: + return new B44Compression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs similarity index 91% rename from src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs rename to src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs index a7b584ed4..d0964bf33 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressionType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrCompression.cs @@ -1,9 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Exr.Compression; +namespace SixLabors.ImageSharp.Formats.Exr.Constants; -internal enum ExrCompressionType +/// +/// Enumeration representing the compression formats defined by the EXR file-format. +/// +public enum ExrCompression { /// /// Pixel data is not compressed. diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs new file mode 100644 index 000000000..c2a752c61 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression; + +internal abstract class ExrBaseCompressor : ExrBaseCompression +{ + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image to. + /// The memory allocator. + /// Bytes per block. + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock) + : base(allocator, bytesPerBlock) + => this.Output = output; + + /// + /// Gets the compression method to use. + /// + public abstract ExrCompression Method { get; } + + /// + /// Gets the output stream to write the compressed image to. + /// + public Stream Output { get; } + + /// + /// Does any initialization required for the compression. + /// + /// The number of rows per strip. + public abstract void Initialize(int rowsPerStrip); + + /// + /// Compresses a strip of the image. + /// + /// Image rows to compress. + /// Image height. + public abstract void CompressStrip(Span rows, int height); +} diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 38e9c3ec9..7f53d8a24 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -79,7 +79,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// /// Gets or sets the compression method. /// - private ExrCompressionType Compression { get; set; } + private ExrCompression Compression { get; set; } /// /// Gets or sets the image data type, either RGB, RGBA or gray. @@ -453,7 +453,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore IList channels = null; ExrBox2i? dataWindow = null; - ExrCompressionType? compression = null; + ExrCompression? compression = null; ExrBox2i? displayWindow = null; ExrLineOrder? lineOrder = null; float? aspectRatio = null; @@ -471,7 +471,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore channels = this.ReadChannelList(stream, attribute.Length); break; case ExrConstants.AttributeNames.Compression: - compression = (ExrCompressionType)stream.ReadByte(); + compression = (ExrCompression)stream.ReadByte(); break; case ExrConstants.AttributeNames.DataWindow: dataWindow = this.ReadBoxInteger(stream); @@ -648,11 +648,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { switch (this.Compression) { - case ExrCompressionType.None: - case ExrCompressionType.Zip: - case ExrCompressionType.Zips: - case ExrCompressionType.RunLengthEncoded: - case ExrCompressionType.B44: + case ExrCompression.None: + case ExrCompression.Zip: + case ExrCompression.Zips: + case ExrCompression.RunLengthEncoded: + case ExrCompression.B44: return true; } @@ -757,12 +757,12 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { switch (this.Compression) { - case ExrCompressionType.Zip: - case ExrCompressionType.Pxr24: + case ExrCompression.Zip: + case ExrCompression.Pxr24: return 16; - case ExrCompressionType.B44: - case ExrCompressionType.B44A: - case ExrCompressionType.Piz: + case ExrCompression.B44: + case ExrCompression.B44A: + case ExrCompression.Piz: return 32; default: diff --git a/src/ImageSharp/Formats/Exr/ExrEncoder.cs b/src/ImageSharp/Formats/Exr/ExrEncoder.cs index 15e10ba2f..2ea1b9116 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoder.cs @@ -15,6 +15,11 @@ public sealed class ExrEncoder : ImageEncoder /// public ExrPixelType? PixelType { get; set; } + /// + /// Gets the compression type to use. + /// + public ExrCompression? Compression { get; init; } + /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 400915d22..ff5287b75 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -6,7 +6,6 @@ using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Channels; -using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -77,7 +76,7 @@ internal sealed class ExrEncoderCore this.pixelType ??= exrMetadata.PixelType; int width = image.Width; int height = image.Height; - ExrCompressionType compression = ExrCompressionType.None; + ExrCompression compression = ExrCompression.None; float aspectRatio = 1.0f; ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); @@ -311,7 +310,7 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } - private void WriteCompression(Stream stream, ExrCompressionType compression) + private void WriteCompression(Stream stream, ExrCompression compression) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); diff --git a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index 35611bb6e..afda62bf9 100644 --- a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; @@ -10,7 +9,7 @@ internal class ExrHeaderAttributes { public ExrHeaderAttributes( IList channels, - ExrCompressionType compression, + ExrCompression compression, ExrBox2i dataWindow, ExrBox2i displayWindow, ExrLineOrder lineOrder, @@ -36,7 +35,7 @@ internal class ExrHeaderAttributes public IList Channels { get; set; } - public ExrCompressionType Compression { get; set; } + public ExrCompression Compression { get; set; } public ExrBox2i DataWindow { get; set; } diff --git a/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs index f668dcfef..51419ec95 100644 --- a/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs +++ b/src/ImageSharp/Formats/Exr/ExrThrowHelper.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; + namespace SixLabors.ImageSharp.Formats.Exr; /// @@ -8,15 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Exr; /// internal static class ExrThrowHelper { + [DoesNotReturn] public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [DoesNotReturn] public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + [DoesNotReturn] public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); + [DoesNotReturn] public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); + [DoesNotReturn] public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); + [DoesNotReturn] public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); + + [DoesNotReturn] + public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); } From 3ff9d244222c5802ff6d2c13b9278c1277cac0c3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 21 Mar 2026 15:21:26 +0100 Subject: [PATCH 074/240] Refactor exr encoder for compression --- .../Compression/Compressors/NoCompressor.cs | 8 - .../Compressors/NoneExrCompressor.cs | 31 ++++ .../{ZipCompressor.cs => ZipExrCompressor.cs} | 6 +- ...B44Compression.cs => B44ExrCompression.cs} | 4 +- ...pression.cs => RunLengthExrCompression.cs} | 4 +- .../Exr/Compression/ExrCompressorFactory.cs | 11 +- .../Exr/Compression/ExrDecompressorFactory.cs | 13 +- .../Formats/Exr/ExrBaseCompressor.cs | 4 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 4 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 151 ++++++++++++++++-- 10 files changed, 196 insertions(+), 40 deletions(-) delete mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs create mode 100644 src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs rename src/ImageSharp/Formats/Exr/Compression/Compressors/{ZipCompressor.cs => ZipExrCompressor.cs} (83%) rename src/ImageSharp/Formats/Exr/Compression/Decompressors/{B44Compression.cs => B44ExrCompression.cs} (97%) rename src/ImageSharp/Formats/Exr/Compression/Decompressors/{RunLengthCompression.cs => RunLengthExrCompression.cs} (93%) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs deleted file mode 100644 index 9a9bc8fff..000000000 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoCompressor.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; - -internal class NoCompressor -{ -} diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs new file mode 100644 index 000000000..e8f3a0686 --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; + +internal class NoneExrCompressor : ExrBaseCompressor +{ + public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock) + : base(output, allocator, bytesPerBlock) + { + } + + /// + public override ExrCompression Method => ExrCompression.Zip; + + /// + public override void Initialize(int rowsPerBlock) + { + } + + /// + public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); + + /// + protected override void Dispose(bool disposing) + { + } +} diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs similarity index 83% rename from src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs rename to src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index d205c2f1a..af93664f3 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -7,13 +7,13 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; -internal class ZipCompressor : ExrBaseCompressor +internal class ZipExrCompressor : ExrBaseCompressor { private readonly DeflateCompressionLevel compressionLevel; private readonly MemoryStream memoryStream = new(); - public ZipCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) + public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) : base(output, allocator, bytesPerBlock) => this.compressionLevel = compressionLevel; @@ -21,7 +21,7 @@ internal class ZipCompressor : ExrBaseCompressor public override ExrCompression Method => ExrCompression.Zip; /// - public override void Initialize(int rowsPerStrip) + public override void Initialize(int rowsPerBlock) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs similarity index 97% rename from src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index d0f9eb6ef..1c6afdfd9 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; -internal class B44Compression : ExrBaseDecompressor +internal class B44ExrCompression : ExrBaseDecompressor { private readonly int width; @@ -24,7 +24,7 @@ internal class B44Compression : ExrBaseDecompressor private IMemoryOwner tmpBuffer; - public B44Compression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) + public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) : base(allocator, bytesPerBlock) { this.width = width; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs similarity index 93% rename from src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs rename to src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 15538a813..bc5a9a557 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -7,13 +7,13 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; -internal class RunLengthCompression : ExrBaseDecompressor +internal class RunLengthExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; private readonly ushort[] s = new ushort[16]; - public RunLengthCompression(MemoryAllocator allocator, uint uncompressedBytes) + public RunLengthExrCompression(MemoryAllocator allocator, uint uncompressedBytes) : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index fc403be3a..4482ca763 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -12,13 +12,20 @@ internal static class ExrCompressorFactory { public static ExrBaseCompressor Create( ExrCompression method, - Stream output, MemoryAllocator allocator, + Stream output, int width, - DeflateCompressionLevel compressionLevel) + int height, + uint bytesPerBlock, + uint rowsPerBlock, + int channelCount, + DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) { switch (method) { + case ExrCompression.None: + return new NoneExrCompressor(output, allocator, bytesPerBlock); + default: throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 6764a2a13..fb620e985 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -9,7 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal static class ExrDecompressorFactory { - public static ExrBaseDecompressor Create(ExrCompression method, MemoryAllocator memoryAllocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) + public static ExrBaseDecompressor Create( + ExrCompression method, + MemoryAllocator memoryAllocator, + int width, + int height, + uint bytesPerBlock, + uint rowsPerBlock, + int channelCount) { switch (method) { @@ -20,9 +27,9 @@ internal static class ExrDecompressorFactory case ExrCompression.Zip: return new ZipExrCompression(memoryAllocator, bytesPerBlock); case ExrCompression.RunLengthEncoded: - return new RunLengthCompression(memoryAllocator, bytesPerBlock); + return new RunLengthExrCompression(memoryAllocator, bytesPerBlock); case ExrCompression.B44: - return new B44Compression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); + return new B44ExrCompression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index c2a752c61..ee21acb70 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -31,8 +31,8 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// /// Does any initialization required for the compression. /// - /// The number of rows per strip. - public abstract void Initialize(int rowsPerStrip); + /// The number of rows per block. + public abstract void Initialize(int rowsPerBlock); /// /// Compresses a strip of the image. diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 7f53d8a24..c7c05a9c5 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -147,7 +147,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount); for (uint y = 0; y < height; y += rowsPerBlock) { @@ -200,7 +200,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount); for (uint y = 0; y < height; y += rowsPerBlock) { diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index ff5287b75..7151a642a 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -5,7 +5,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; -using System.Threading.Channels; +using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -47,7 +47,7 @@ internal sealed class ExrEncoderCore /// Initializes a new instance of the class. /// /// The encoder with options. - /// The configuration. + /// The configuration. /// The memory manager. public ExrEncoderCore(ExrEncoder encoder, Configuration configuration, MemoryAllocator memoryAllocator) { @@ -126,7 +126,7 @@ internal sealed class ExrEncoderCore { case ExrPixelType.Half: case ExrPixelType.Float: - this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes); + this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes, channels, compression); break; case ExrPixelType.UnsignedInt: this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); @@ -134,43 +134,60 @@ internal sealed class ExrEncoderCore } } - private void EncodeFloatingPointPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + private void EncodeFloatingPointPixelData( + Stream stream, + Buffer2D pixels, + int width, + int height, + uint rowSizeBytes, + List channels, + ExrCompression compression) where TPixel : unmanaged, IPixel { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width); + uint rowsPerBlock = this.RowsPerBlock(compression); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int channelCount = channels.Count; + + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount); + for (int y = 0; y < height; y++) { + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + stream.Write(this.buffer.AsSpan(0, 4)); + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { - var vector4 = pixelRowSpan[x].ToVector4(); + Vector4 vector4 = pixelRowSpan[x].ToVector4(); redBuffer[x] = vector4.X; greenBuffer[x] = vector4.Y; blueBuffer[x] = vector4.Z; } - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); - - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - switch (this.pixelType) { case ExrPixelType.Float: - this.WriteSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + this.WriteSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); break; case ExrPixelType.Half: - this.WriteHalfSingleRow(stream, width, blueBuffer, greenBuffer, redBuffer); + this.WriteHalfSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); break; } + + compressor.CompressStrip(blockBuffer.GetSpan(), 1); } } @@ -240,6 +257,50 @@ internal sealed class ExrEncoderCore } } + private void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this.WriteSingle(buffer.Slice(offset), blueBuffer[x]); + offset += 4; + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(buffer.Slice(offset), greenBuffer[x]); + offset += 4; + } + + for (int x = 0; x < width; x++) + { + this.WriteSingle(buffer.Slice(offset), redBuffer[x]); + offset += 4; + } + } + + private void WriteHalfSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + { + int offset = 0; + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(buffer.Slice(offset), blueBuffer[x]); + offset += 2; + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(buffer.Slice(offset), greenBuffer[x]); + offset += 2; + } + + for (int x = 0; x < width; x++) + { + this.WriteHalfSingle(buffer.Slice(offset), redBuffer[x]); + offset += 2; + } + } + private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { for (int x = 0; x < width; x++) @@ -420,6 +481,12 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] + private unsafe void WriteSingle(Span buffer, float value) + { + BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); + } + [MethodImpl(InliningOptions.ShortMethod)] private void WriteHalfSingle(Stream stream, float value) { @@ -428,10 +495,62 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 2)); } + + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteHalfSingle(Span buffer, float value) + { + ushort valueAsShort = HalfTypeHelper.Pack(value); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + } + [MethodImpl(InliningOptions.ShortMethod)] private void WriteUnsignedInt(Stream stream, uint value) { BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); stream.Write(this.buffer.AsSpan(0, 4)); } + + // TODO: avoid code duplication: This code is duplicate in the decoder. + private uint CalculateBytesPerRow(List channels, uint width) + { + uint bytesPerRow = 0; + foreach (ExrChannelInfo channelInfo in channels) + { + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) + { + if (channelInfo.PixelType == ExrPixelType.Half) + { + bytesPerRow += 2 * width; + } + else + { + bytesPerRow += 4 * width; + } + } + } + + return bytesPerRow; + } + + // TODO: avoid code duplication: This code is duplicate in the decoder. + private uint RowsPerBlock(ExrCompression compression) + { + switch (compression) + { + case ExrCompression.Zip: + case ExrCompression.Pxr24: + return 16; + case ExrCompression.B44: + case ExrCompression.B44A: + case ExrCompression.Piz: + return 32; + + default: + return 1; + } + } } From 0d515147d019f6539f453b10f9bfbedcc4331195 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 21 Mar 2026 19:57:10 +0100 Subject: [PATCH 075/240] Use compressor when writing pixel row data with exr encoder --- .../Compressors/NoneExrCompressor.cs | 6 +- .../Compressors/ZipExrCompressor.cs | 3 +- .../Exr/Compression/ExrCompressorFactory.cs | 2 + .../Formats/Exr/ExrBaseCompressor.cs | 5 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 179 ++++++++++-------- 6 files changed, 110 insertions(+), 87 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index e8f3a0686..40ee81cf0 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -22,7 +22,11 @@ internal class NoneExrCompressor : ExrBaseCompressor } /// - public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); + public override uint CompressRowBlock(Span rows, int height) + { + this.Output.Write(rows); + return (uint)rows.Length; + } /// protected override void Dispose(bool disposing) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index af93664f3..e7b695df0 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -26,7 +26,7 @@ internal class ZipExrCompressor : ExrBaseCompressor } /// - public override void CompressStrip(Span rows, int height) + public override uint CompressRowBlock(Span rows, int height) { this.memoryStream.Seek(0, SeekOrigin.Begin); using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) @@ -38,6 +38,7 @@ internal class ZipExrCompressor : ExrBaseCompressor int size = (int)this.memoryStream.Position; byte[] buffer = this.memoryStream.GetBuffer(); this.Output.Write(buffer, 0, size); + return (uint)buffer.Length; } /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index 4482ca763..e8e7af471 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -25,6 +25,8 @@ internal static class ExrCompressorFactory { case ExrCompression.None: return new NoneExrCompressor(output, allocator, bytesPerBlock); + case ExrCompression.Zip: + return new ZipExrCompressor(output, allocator, bytesPerBlock, compressionLevel); default: throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index ee21acb70..cf547eddb 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -35,9 +35,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression public abstract void Initialize(int rowsPerBlock); /// - /// Compresses a strip of the image. + /// Compresses a block of rows of the image. /// /// Image rows to compress. /// Image height. - public abstract void CompressStrip(Span rows, int height); + /// Number of bytes of of the compressed data. + public abstract uint CompressRowBlock(Span rows, int height); } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index c7c05a9c5..7165720d3 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -53,7 +53,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore : base(options.GeneralOptions) { this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 7151a642a..9e6341344 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading.Channels; using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; @@ -54,8 +55,14 @@ internal sealed class ExrEncoderCore this.configuration = configuration; this.encoder = encoder; this.memoryAllocator = memoryAllocator; + this.Compression = encoder.Compression ?? ExrCompression.None; } + /// + /// Gets or sets the compression implementation to use when encoding the image. + /// + internal ExrCompression Compression { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -76,7 +83,6 @@ internal sealed class ExrEncoderCore this.pixelType ??= exrMetadata.PixelType; int width = image.Width; int height = image.Height; - ExrCompression compression = ExrCompression.None; float aspectRatio = 1.0f; ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); @@ -91,7 +97,7 @@ internal sealed class ExrEncoderCore ]; ExrHeaderAttributes header = new( channels, - compression, + this.Compression, dataWindow, displayWindow, lineOrder, @@ -115,31 +121,40 @@ internal sealed class ExrEncoderCore // Write EXR header. this.WriteHeader(stream, header); - // Write offsets to each pixel row. + // Next is offsets table to each pixel row, which will be written after the pixel data was written. int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; int numberOfChannels = 3; uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); - this.WriteRowOffsets(stream, height, rowSizeBytes); + ulong startOfRowOffsetData = (ulong)stream.Position; + stream.Position += 8 * height; // Write pixel data. switch (this.pixelType) { case ExrPixelType.Half: case ExrPixelType.Float: - this.EncodeFloatingPointPixelData(stream, pixels, width, height, rowSizeBytes, channels, compression); + { + ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression); + stream.Position = (long)startOfRowOffsetData; + this.WriteRowOffsets(stream, height, rowOffsets); break; + } + case ExrPixelType.UnsignedInt: - this.EncodeUnsignedIntPixelData(stream, pixels, width, height, rowSizeBytes); + { + ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression); + stream.Position = (long)startOfRowOffsetData; + this.WriteRowOffsets(stream, height, rowOffsets); break; + } } } - private void EncodeFloatingPointPixelData( + private ulong[] EncodeFloatingPointPixelData( Stream stream, Buffer2D pixels, int width, int height, - uint rowSizeBytes, List channels, ExrCompression compression) where TPixel : unmanaged, IPixel @@ -150,24 +165,26 @@ internal sealed class ExrEncoderCore int channelCount = channels.Count; using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); - using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); + using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount); + ulong[] rowOffsets = new ulong[height]; for (int y = 0; y < height; y++) { + rowOffsets[y] = (ulong)stream.Position; + // Write row index. BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); stream.Write(this.buffer.AsSpan(0, 4)); - // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); - stream.Write(this.buffer.AsSpan(0, 4)); - + // At this point, it is not yet known how mcuh bytes the compressed data will take up, keep stream position. + long pixelDataSizePos = stream.Position; Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + stream.Position = pixelDataSizePos + 4; for (int x = 0; x < width; x++) { @@ -177,32 +194,67 @@ internal sealed class ExrEncoderCore blueBuffer[x] = vector4.Z; } + // Write pixel data to buffer. switch (this.pixelType) { case ExrPixelType.Float: - this.WriteSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + this.WriteSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); break; case ExrPixelType.Half: - this.WriteHalfSingleRow(blockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + this.WriteHalfSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); break; } - compressor.CompressStrip(blockBuffer.GetSpan(), 1); + // Write compressed pixel row data to stream. + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1); + long positionAfterPixelData = stream.Position; + + // Write pixel row data size. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); + stream.Position = pixelDataSizePos; + stream.Write(this.buffer.AsSpan(0, 4)); + stream.Position = positionAfterPixelData; } + + return rowOffsets; } - private void EncodeUnsignedIntPixelData(Stream stream, Buffer2D pixels, int width, int height, uint rowSizeBytes) + private ulong[] EncodeUnsignedIntPixelData( + Stream stream, + Buffer2D pixels, + int width, + int height, + List channels, + ExrCompression compression) where TPixel : unmanaged, IPixel { - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3); + uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width); + uint rowsPerBlock = this.RowsPerBlock(compression); + uint bytesPerBlock = bytesPerRow * rowsPerBlock; + int channelCount = channels.Count; + + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); + using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount); + Rgb96 rgb = default; + ulong[] rowOffsets = new ulong[height]; for (int y = 0; y < height; y++) { + rowOffsets[y] = (ulong)stream.Position; + + // Write row index. + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + stream.Write(this.buffer.AsSpan(0, 4)); + + // At this point, it is not yet known how mcuh bytes the compressed data will take up, keep stream position. + long pixelDataSizePos = stream.Position; Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + stream.Position = pixelDataSizePos + 4; for (int x = 0; x < width; x++) { @@ -214,16 +266,21 @@ internal sealed class ExrEncoderCore blueBuffer[x] = rgb.B; } - // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); - stream.Write(this.buffer.AsSpan(0, 4)); + // Write row data to buffer. + this.WriteUnsignedIntRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + + // Write pixel row data compressed to the stream. + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1); + long positionAfterPixelData = stream.Position; // Write pixel row data size. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, rowSizeBytes); + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); + stream.Position = pixelDataSizePos; stream.Write(this.buffer.AsSpan(0, 4)); - - this.WriteUnsignedIntRow(stream, width, blueBuffer, greenBuffer, redBuffer); + stream.Position = positionAfterPixelData; } + + return rowOffsets; } private void WriteHeader(Stream stream, ExrHeaderAttributes header) @@ -239,24 +296,6 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } - private void WriteSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) - { - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, blueBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, greenBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteSingle(stream, redBuffer[x]); - } - } - private void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; @@ -301,51 +340,36 @@ internal sealed class ExrEncoderCore } } - private void WriteHalfSingleRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) - { - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, blueBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, greenBuffer[x]); - } - - for (int x = 0; x < width; x++) - { - this.WriteHalfSingle(stream, redBuffer[x]); - } - } - - private void WriteUnsignedIntRow(Stream stream, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private void WriteUnsignedIntRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { + int offset = 0; for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(stream, blueBuffer[x]); + this.WriteUnsignedInt(buffer.Slice(offset), blueBuffer[x]); + offset += 4; } for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(stream, greenBuffer[x]); + this.WriteUnsignedInt(buffer.Slice(offset), greenBuffer[x]); + offset += 4; } for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(stream, redBuffer[x]); + this.WriteUnsignedInt(buffer.Slice(offset), redBuffer[x]); + offset += 4; } } - private void WriteRowOffsets(Stream stream, int height, uint rowSizeBytes) + private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets) { - ulong startOfPixelData = (ulong)stream.Position + (8 * (ulong)height); - ulong offset = startOfPixelData; + ulong startOfRowOffsetData = (ulong)stream.Position; + ulong offset = startOfRowOffsetData; for (int i = 0; i < height; i++) { - BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, offset); + BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, rowOffsets[i]); stream.Write(this.buffer); - offset += 4 + 4 + rowSizeBytes; } } @@ -482,19 +506,7 @@ internal sealed class ExrEncoderCore } [MethodImpl(InliningOptions.ShortMethod)] - private unsafe void WriteSingle(Span buffer, float value) - { - BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void WriteHalfSingle(Stream stream, float value) - { - ushort valueAsShort = HalfTypeHelper.Pack(value); - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); - stream.Write(this.buffer.AsSpan(0, 2)); - } - + private unsafe void WriteSingle(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); [MethodImpl(InliningOptions.ShortMethod)] private void WriteHalfSingle(Span buffer, float value) @@ -510,6 +522,9 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 4)); } + [MethodImpl(InliningOptions.ShortMethod)] + private void WriteUnsignedInt(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + // TODO: avoid code duplication: This code is duplicate in the decoder. private uint CalculateBytesPerRow(List channels, uint width) { From 614a7f6e84bfe13b5224913490ffabd2601d1f1a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 22 Mar 2026 16:22:55 +0100 Subject: [PATCH 076/240] Re-arrange pixeldata and add predictor for zip compressed data --- .../Compressors/ZipExrCompressor.cs | 25 +++++++++++++++++-- .../Exr/Compression/ExrCompressorFactory.cs | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index e7b695df0..8373e92e2 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -11,7 +11,7 @@ internal class ZipExrCompressor : ExrBaseCompressor { private readonly DeflateCompressionLevel compressionLevel; - private readonly MemoryStream memoryStream = new(); + private MemoryStream memoryStream = new(); public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) : base(output, allocator, bytesPerBlock) @@ -28,6 +28,25 @@ internal class ZipExrCompressor : ExrBaseCompressor /// public override uint CompressRowBlock(Span rows, int height) { + // Re-oder pixel values. + int n = rows.Length; + int t1 = 0; + int t2 = (n + 1) >> 1; + for (int i = 0; i < n; i++) + { + bool isOdd = (i & 1) == 1; + rows[isOdd ? t2++ : t1++] = rows[i]; + } + + // Predictor. + byte p = rows[0]; + for (int i = 1; i < rows.Length; i++) + { + int d = (rows[i] - p + 128 + 256) & 255; + p = rows[i]; + rows[i] = (byte)d; + } + this.memoryStream.Seek(0, SeekOrigin.Begin); using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) { @@ -38,7 +57,9 @@ internal class ZipExrCompressor : ExrBaseCompressor int size = (int)this.memoryStream.Position; byte[] buffer = this.memoryStream.GetBuffer(); this.Output.Write(buffer, 0, size); - return (uint)buffer.Length; + + this.memoryStream = new(); + return (uint)size; } /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index e8e7af471..a87372748 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -25,7 +25,7 @@ internal static class ExrCompressorFactory { case ExrCompression.None: return new NoneExrCompressor(output, allocator, bytesPerBlock); - case ExrCompression.Zip: + case ExrCompression.Zips: return new ZipExrCompressor(output, allocator, bytesPerBlock, compressionLevel); default: From 5ee52ef403cfcd4a9b70e014a3fa2ded1eecabf1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 22 Mar 2026 19:26:58 +0100 Subject: [PATCH 077/240] Fix issue in re-ordering pixel value, does not work inplace --- .../Compressors/ZipExrCompressor.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 8373e92e2..17203b6ba 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -13,9 +13,14 @@ internal class ZipExrCompressor : ExrBaseCompressor private MemoryStream memoryStream = new(); + private readonly System.Buffers.IMemoryOwner buffer; + public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) : base(output, allocator, bytesPerBlock) - => this.compressionLevel = compressionLevel; + { + this.compressionLevel = compressionLevel; + this.buffer = allocator.Allocate((int)bytesPerBlock); + } /// public override ExrCompression Method => ExrCompression.Zip; @@ -30,27 +35,29 @@ internal class ZipExrCompressor : ExrBaseCompressor { // Re-oder pixel values. int n = rows.Length; + Span reordered = this.buffer.GetSpan(); int t1 = 0; int t2 = (n + 1) >> 1; for (int i = 0; i < n; i++) { bool isOdd = (i & 1) == 1; - rows[isOdd ? t2++ : t1++] = rows[i]; + reordered[isOdd ? t2++ : t1++] = rows[i]; } // Predictor. - byte p = rows[0]; + Span predicted = reordered; + byte p = predicted[0]; for (int i = 1; i < rows.Length; i++) { - int d = (rows[i] - p + 128 + 256) & 255; - p = rows[i]; - rows[i] = (byte)d; + int d = (predicted[i] - p + 128 + 256) & 255; + p = predicted[i]; + predicted[i] = (byte)d; } this.memoryStream.Seek(0, SeekOrigin.Begin); using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) { - stream.Write(rows); + stream.Write(predicted); stream.Flush(); } From 43f952cf00e0ff8759f967ff95beafa92ab1b526 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 23 Mar 2026 09:37:53 +0100 Subject: [PATCH 078/240] Throw InvalidImageContentException when frame control chunk does not have enough data --- src/ImageSharp/Formats/Png/Chunks/FrameControl.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs index 91f79c815..7288df8a0 100644 --- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs +++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs @@ -147,7 +147,13 @@ internal readonly struct FrameControl /// The data to parse. /// The parsed fcTL. public static FrameControl Parse(ReadOnlySpan data) - => new( + { + if (data.Length < Size) + { + PngThrowHelper.ThrowInvalidImageContentException("The Frame Control Chunk does not contain enough data!"); + } + + return new( sequenceNumber: BinaryPrimitives.ReadUInt32BigEndian(data[..4]), width: BinaryPrimitives.ReadUInt32BigEndian(data[4..8]), height: BinaryPrimitives.ReadUInt32BigEndian(data[8..12]), @@ -157,4 +163,5 @@ internal readonly struct FrameControl delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]), disposalMode: (FrameDisposalMode)(data[24] + 1), blendMode: (FrameBlendMode)data[25]); + } } From 4e81c02b5dcda32dddbc62dc7e1679c6b31f41c5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 23 Mar 2026 09:44:15 +0100 Subject: [PATCH 079/240] Add test case for Issue #3093 --- .../Formats/Png/Chunks/FrameControl.cs | 2 +- .../Formats/Png/PngDecoderTests.Chunks.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs index 7288df8a0..0038b4134 100644 --- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs +++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs @@ -150,7 +150,7 @@ internal readonly struct FrameControl { if (data.Length < Size) { - PngThrowHelper.ThrowInvalidImageContentException("The Frame Control Chunk does not contain enough data!"); + PngThrowHelper.ThrowInvalidImageContentException("The frame control chunk does not contain enough data!"); } return new( diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 03dc04018..ed33f7163 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -92,6 +92,22 @@ public partial class PngDecoderTests Assert.Equal("pHYs chunk is too short", exception.Message); } + // https://github.com/SixLabors/ImageSharp/issues/3093 + [Fact] + public void Decode_TruncatedFrameControlChunk_ExceptionIsThrown() + { + // PNG signature + truncated frame control chunk + byte[] payload = Convert.FromHexString( + "89504e470d0a1a0a424d3a00000000007f000000000028030405060000000100" + + "000101002000000000000000000000000000ff00006663544cff190000000000" + + "010000424d000100000101002000000000"); + + using MemoryStream stream = new(payload); + InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); + + Assert.Equal("The frame control chunk does not contain enough data!", exception.Message); + } + // https://github.com/SixLabors/ImageSharp/issues/3079 [Fact] public void Decode_CompressedTxtChunk_WithTruncatedData_DoesNotThrow() From f212af812db899779395d6a105b5e69a86054e28 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 23 Mar 2026 16:04:38 +0100 Subject: [PATCH 080/240] Cleanup Compressor/Decompressors: Remove not needed fields --- .../Compressors/NoneExrCompressor.cs | 11 +++------- .../Compressors/ZipExrCompressor.cs | 11 +++------- .../Decompressors/B44ExrCompression.cs | 13 ++++++------ .../Decompressors/NoneExrCompression.cs | 6 ++++-- .../Decompressors/RunLengthExrCompression.cs | 6 ++++-- .../Decompressors/ZipExrCompression.cs | 6 ++++-- .../Exr/Compression/ExrBaseCompression.cs | 5 +++-- .../Exr/Compression/ExrBaseDecompressor.cs | 4 ++-- .../Exr/Compression/ExrCompressorFactory.cs | 11 +++++----- .../Exr/Compression/ExrDecompressorFactory.cs | 12 +++++------ .../Formats/Exr/ExrBaseCompressor.cs | 17 ++++++---------- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 4 ++-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 20 +++++++++---------- 13 files changed, 58 insertions(+), 68 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index 40ee81cf0..cd9bec613 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -8,8 +8,8 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; internal class NoneExrCompressor : ExrBaseCompressor { - public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock) - : base(output, allocator, bytesPerBlock) + public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(output, allocator, bytesPerBlock, bytesPerRow) { } @@ -17,12 +17,7 @@ internal class NoneExrCompressor : ExrBaseCompressor public override ExrCompression Method => ExrCompression.Zip; /// - public override void Initialize(int rowsPerBlock) - { - } - - /// - public override uint CompressRowBlock(Span rows, int height) + public override uint CompressRowBlock(Span rows, int rowCount) { this.Output.Write(rows); return (uint)rows.Length; diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 17203b6ba..6b82b7077 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -15,8 +15,8 @@ internal class ZipExrCompressor : ExrBaseCompressor private readonly System.Buffers.IMemoryOwner buffer; - public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, DeflateCompressionLevel compressionLevel) - : base(output, allocator, bytesPerBlock) + public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock, bytesPerRow) { this.compressionLevel = compressionLevel; this.buffer = allocator.Allocate((int)bytesPerBlock); @@ -26,12 +26,7 @@ internal class ZipExrCompressor : ExrBaseCompressor public override ExrCompression Method => ExrCompression.Zip; /// - public override void Initialize(int rowsPerBlock) - { - } - - /// - public override uint CompressRowBlock(Span rows, int height) + public override uint CompressRowBlock(Span rows, int rowCount) { // Re-oder pixel values. int n = rows.Length; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index 1c6afdfd9..bf6d9929e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -12,28 +12,26 @@ internal class B44ExrCompression : ExrBaseDecompressor { private readonly int width; - private readonly int height; - private readonly uint rowsPerBlock; private readonly int channelCount; - private byte[] scratch = new byte[14]; + private readonly byte[] scratch = new byte[14]; private ushort[] s = new ushort[16]; - private IMemoryOwner tmpBuffer; + private readonly IMemoryOwner tmpBuffer; - public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, int width, int height, uint rowsPerBlock, int channelCount) - : base(allocator, bytesPerBlock) + public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) + : base(allocator, bytesPerBlock, bytesPerRow) { this.width = width; - this.height = height; this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); } + /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span outputBuffer = MemoryMarshal.Cast(buffer); @@ -187,5 +185,6 @@ internal class B44ExrCompression : ExrBaseDecompressor } } + /// protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 75bf67cb2..26c69db8c 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -8,14 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; internal class NoneExrCompression : ExrBaseDecompressor { - public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock) - : base(allocator, bytesPerBlock) + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow) { } + /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); + /// protected override void Dispose(bool disposing) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index bc5a9a557..12f5fc8ab 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -13,9 +13,10 @@ internal class RunLengthExrCompression : ExrBaseDecompressor private readonly ushort[] s = new ushort[16]; - public RunLengthExrCompression(MemoryAllocator allocator, uint uncompressedBytes) - : base(allocator, uncompressedBytes) => this.tmpBuffer = allocator.Allocate((int)uncompressedBytes); + public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); @@ -78,5 +79,6 @@ internal class RunLengthExrCompression : ExrBaseDecompressor return (byte)nextByte; } + /// protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 95d4c0313..542463452 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -13,9 +13,10 @@ internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock) - : base(allocator, bytesPerBlock) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); @@ -52,5 +53,6 @@ internal class ZipExrCompression : ExrBaseDecompressor Interleave(uncompressed, (uint)totalRead, buffer); } + /// protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index e510135c2..57e6b2a26 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -9,10 +9,11 @@ internal abstract class ExrBaseCompression : IDisposable { private bool isDisposed; - protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock) + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) { this.Allocator = allocator; this.BytesPerBlock = bytesPerBlock; + this.BytesPerRow = bytesPerRow; } /// @@ -28,7 +29,7 @@ internal abstract class ExrBaseCompression : IDisposable /// /// Gets the bytes per row. /// - public int BytesPerRow { get; } + public uint BytesPerRow { get; } /// /// Gets the uncompressed bytes per block. diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 0dd7d0110..1bbf36d76 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -8,8 +8,8 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression; internal abstract class ExrBaseDecompressor : ExrBaseCompression { - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock) - : base(allocator, bytesPerBlock) + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index a87372748..24f396e16 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -14,19 +14,18 @@ internal static class ExrCompressorFactory ExrCompression method, MemoryAllocator allocator, Stream output, - int width, - int height, uint bytesPerBlock, - uint rowsPerBlock, - int channelCount, + uint bytesPerRow, DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) { switch (method) { case ExrCompression.None: - return new NoneExrCompressor(output, allocator, bytesPerBlock); + return new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow); case ExrCompression.Zips: - return new ZipExrCompressor(output, allocator, bytesPerBlock, compressionLevel); + return new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel); + case ExrCompression.Zip: + return new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel); default: throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index fb620e985..2696a289c 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -13,23 +13,23 @@ internal static class ExrDecompressorFactory ExrCompression method, MemoryAllocator memoryAllocator, int width, - int height, uint bytesPerBlock, + uint bytesPerRow, uint rowsPerBlock, int channelCount) { switch (method) { case ExrCompression.None: - return new NoneExrCompression(memoryAllocator, bytesPerBlock); + return new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); case ExrCompression.Zips: - return new ZipExrCompression(memoryAllocator, bytesPerBlock); + return new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); case ExrCompression.Zip: - return new ZipExrCompression(memoryAllocator, bytesPerBlock); + return new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); case ExrCompression.RunLengthEncoded: - return new RunLengthExrCompression(memoryAllocator, bytesPerBlock); + return new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); case ExrCompression.B44: - return new B44ExrCompression(memoryAllocator, bytesPerBlock, width, height, rowsPerBlock, channelCount); + return new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount); default: throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); } diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index cf547eddb..1f464ec67 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -13,9 +13,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// /// The output stream to write the compressed image to. /// The memory allocator. - /// Bytes per block. - protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock) - : base(allocator, bytesPerBlock) + /// Bytes per row block. + /// Bytes per pixel row. + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow) => this.Output = output; /// @@ -28,17 +29,11 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// public Stream Output { get; } - /// - /// Does any initialization required for the compression. - /// - /// The number of rows per block. - public abstract void Initialize(int rowsPerBlock); - /// /// Compresses a block of rows of the image. /// /// Image rows to compress. - /// Image height. + /// The number of rows to compress. /// Number of bytes of of the compressed data. - public abstract uint CompressRowBlock(Span rows, int height); + public abstract uint CompressRowBlock(Span rows, int rowCount); } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 7165720d3..dbc6b7bf7 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -147,7 +147,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); for (uint y = 0; y < height; y += rowsPerBlock) { @@ -200,7 +200,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, height, bytesPerBlock, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); for (uint y = 0; y < height; y += rowsPerBlock) { diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 9e6341344..a7b37eda2 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; -using System.Threading.Channels; using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; @@ -32,7 +31,7 @@ internal sealed class ExrEncoderCore /// /// The global configuration. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// The encoder with options. @@ -170,7 +169,7 @@ internal sealed class ExrEncoderCore Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); ulong[] rowOffsets = new ulong[height]; for (int y = 0; y < height; y++) @@ -181,7 +180,7 @@ internal sealed class ExrEncoderCore BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); stream.Write(this.buffer.AsSpan(0, 4)); - // At this point, it is not yet known how mcuh bytes the compressed data will take up, keep stream position. + // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; @@ -195,18 +194,19 @@ internal sealed class ExrEncoderCore } // Write pixel data to buffer. + Span rowBlockSpan = rowBlockBuffer.GetSpan(); switch (this.pixelType) { case ExrPixelType.Float: - this.WriteSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); break; case ExrPixelType.Half: - this.WriteHalfSingleRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); break; } - // Write compressed pixel row data to stream. - uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1); + // Write compressed pixel row data to the stream. + uint compressedBytes = compressor.CompressRowBlock(rowBlockSpan, (int)rowsPerBlock); long positionAfterPixelData = stream.Position; // Write pixel row data size. @@ -239,7 +239,7 @@ internal sealed class ExrEncoderCore Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, width, height, bytesPerBlock, rowsPerBlock, channelCount); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); Rgb96 rgb = default; ulong[] rowOffsets = new ulong[height]; @@ -251,7 +251,7 @@ internal sealed class ExrEncoderCore BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); stream.Write(this.buffer.AsSpan(0, 4)); - // At this point, it is not yet known how mcuh bytes the compressed data will take up, keep stream position. + // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; From f9df91108a8600696e0b1fa700fbe355c4a5a09f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 23 Mar 2026 18:14:00 +0100 Subject: [PATCH 081/240] Add support for writing exr with zip compression with 16 rows per block --- .../Compressors/ZipExrCompressor.cs | 6 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 4 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 78 +++++++++++-------- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 6b82b7077..8a7b78460 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -29,8 +29,8 @@ internal class ZipExrCompressor : ExrBaseCompressor public override uint CompressRowBlock(Span rows, int rowCount) { // Re-oder pixel values. - int n = rows.Length; - Span reordered = this.buffer.GetSpan(); + Span reordered = this.buffer.GetSpan()[..(int)(rowCount * this.BytesPerRow)]; + int n = reordered.Length; int t1 = 0; int t2 = (n + 1) >> 1; for (int i = 0; i < n; i++) @@ -42,7 +42,7 @@ internal class ZipExrCompressor : ExrBaseCompressor // Predictor. Span predicted = reordered; byte p = predicted[0]; - for (int i = 1; i < rows.Length; i++) + for (int i = 1; i < predicted.Length; i++) { int d = (predicted[i] - p + 128 + 256) & 255; p = predicted[i]; diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index dbc6b7bf7..f8eb17f4e 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -142,7 +142,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span redPixelData = rowBuffer.GetSpan()[..width]; Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); @@ -195,7 +195,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using IMemoryOwner rowBuffer = this.memoryAllocator.Allocate(width * 4); using IMemoryOwner decompressedPixelDataBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock); Span decompressedPixelData = decompressedPixelDataBuffer.GetSpan(); - Span redPixelData = rowBuffer.GetSpan().Slice(0, width); + Span redPixelData = rowBuffer.GetSpan()[..width]; Span greenPixelData = rowBuffer.GetSpan().Slice(width, width); Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index a7b37eda2..a89caf07e 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -172,41 +172,47 @@ internal sealed class ExrEncoderCore using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); ulong[] rowOffsets = new ulong[height]; - for (int y = 0; y < height; y++) + for (uint y = 0; y < height; y += rowsPerBlock) { rowOffsets[y] = (ulong)stream.Position; // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); stream.Write(this.buffer.AsSpan(0, 4)); // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; - for (int x = 0; x < width; x++) + uint rowsInBlockCount = 0; + for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); - redBuffer[x] = vector4.X; - greenBuffer[x] = vector4.Y; - blueBuffer[x] = vector4.Z; - } + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); + for (int x = 0; x < width; x++) + { + Vector4 vector4 = pixelRowSpan[x].ToVector4(); + redBuffer[x] = vector4.X; + greenBuffer[x] = vector4.Y; + blueBuffer[x] = vector4.Z; + } - // Write pixel data to buffer. - Span rowBlockSpan = rowBlockBuffer.GetSpan(); - switch (this.pixelType) - { - case ExrPixelType.Float: - this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); - break; - case ExrPixelType.Half: - this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); - break; + // Write pixel data to row block buffer. + Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); + switch (this.pixelType) + { + case ExrPixelType.Float: + this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + break; + case ExrPixelType.Half: + this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + break; + } + + rowsInBlockCount++; } // Write compressed pixel row data to the stream. - uint compressedBytes = compressor.CompressRowBlock(rowBlockSpan, (int)rowsPerBlock); + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); long positionAfterPixelData = stream.Position; // Write pixel row data size. @@ -243,34 +249,40 @@ internal sealed class ExrEncoderCore Rgb96 rgb = default; ulong[] rowOffsets = new ulong[height]; - for (int y = 0; y < height; y++) + for (uint y = 0; y < height; y += rowsPerBlock) { rowOffsets[y] = (ulong)stream.Position; // Write row index. - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)y); + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); stream.Write(this.buffer.AsSpan(0, 4)); // At this point, it is not yet known how much bytes the compressed data will take up, keep stream position. long pixelDataSizePos = stream.Position; - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); stream.Position = pixelDataSizePos + 4; - for (int x = 0; x < width; x++) + uint rowsInBlockCount = 0; + for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) { - Vector4 vector4 = pixelRowSpan[x].ToVector4(); - Rgb96.FromVector4(vector4); + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); + for (int x = 0; x < width; x++) + { + Vector4 vector4 = pixelRowSpan[x].ToVector4(); + Rgb96.FromVector4(vector4); - redBuffer[x] = rgb.R; - greenBuffer[x] = rgb.G; - blueBuffer[x] = rgb.B; - } + redBuffer[x] = rgb.R; + greenBuffer[x] = rgb.G; + blueBuffer[x] = rgb.B; + } - // Write row data to buffer. - this.WriteUnsignedIntRow(rowBlockBuffer.GetSpan(), width, blueBuffer, greenBuffer, redBuffer); + // Write row data to row block buffer. + Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); + this.WriteUnsignedIntRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + rowsInBlockCount++; + } // Write pixel row data compressed to the stream. - uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), 1); + uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); long positionAfterPixelData = stream.Position; // Write pixel row data size. From a3349efb34ae27ccc3b35fa632a66218c4c16fea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 24 Mar 2026 11:31:07 +0100 Subject: [PATCH 082/240] Add ExrEncoder tests --- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 2 +- .../Formats/Exr/ExrEncoderTests.cs | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index a89caf07e..e393dcf68 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -165,7 +165,7 @@ internal sealed class ExrEncoderCore using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span redBuffer = rgbBuffer.GetSpan()[..width]; Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs new file mode 100644 index 000000000..b16c925f8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrEncoderTests +{ + protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(ExrFormat.Instance); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithNoCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "NoCompression", compression: ExrCompression.None, imageDecoder: ExrDecoder.Instance); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithZipCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipCompression", compression: ExrCompression.Zip, imageDecoder: ExrDecoder.Instance); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithZipsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipsCompression", compression: ExrCompression.Zips, imageDecoder: ExrDecoder.Instance); + + protected static void TestExrEncoderCore( + TestImageProvider provider, + object testOutputDetails, + ExrCompression compression = ExrCompression.None, + bool useExactComparer = true, + float compareTolerance = 0.001f, + IImageDecoder imageDecoder = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + ExrEncoder encoder = new() + { + Compression = compression, + }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder( + provider, + "exr", + testOutputDetails: testOutputDetails, + encoder: encoder, + customComparer: useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder: imageDecoder ?? ReferenceDecoder); + } +} From 9e80da911788e32a050c8cdeaf1737f9515a5932 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 24 Mar 2026 11:43:20 +0100 Subject: [PATCH 083/240] Try fix issue with undisposed buffers --- .../Exr/Compression/Compressors/ZipExrCompressor.cs | 9 +++++++-- .../Exr/Compression/Decompressors/ZipExrCompression.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 8a7b78460..587ecb756 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -11,7 +11,7 @@ internal class ZipExrCompressor : ExrBaseCompressor { private readonly DeflateCompressionLevel compressionLevel; - private MemoryStream memoryStream = new(); + private readonly MemoryStream memoryStream = new(); private readonly System.Buffers.IMemoryOwner buffer; @@ -60,12 +60,17 @@ internal class ZipExrCompressor : ExrBaseCompressor byte[] buffer = this.memoryStream.GetBuffer(); this.Output.Write(buffer, 0, size); - this.memoryStream = new(); + // Reset memory stream for next pixel row. + this.memoryStream.Seek(0, SeekOrigin.Begin); + this.memoryStream.SetLength(0); + return (uint)size; } /// protected override void Dispose(bool disposing) { + this.buffer.Dispose(); + this.memoryStream?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 542463452..b8dc5efa8 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -30,7 +30,7 @@ internal class ZipExrCompression : ExrBaseDecompressor return left > 0 ? left : 0; }); inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); - DeflateStream dataStream = inflateStream.CompressedStream!; + using DeflateStream dataStream = inflateStream.CompressedStream!; int totalRead = 0; while (totalRead < buffer.Length) From d409a3b7d355eec168806b4a530fed6e51d59afe Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 24 Mar 2026 12:21:14 +0100 Subject: [PATCH 084/240] Use magick Reference decoder --- .../Formats/Exr/Compression/Compressors/ZipExrCompressor.cs | 3 ++- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index 587ecb756..dcfbe3ae0 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -11,7 +11,7 @@ internal class ZipExrCompressor : ExrBaseCompressor { private readonly DeflateCompressionLevel compressionLevel; - private readonly MemoryStream memoryStream = new(); + private readonly MemoryStream memoryStream; private readonly System.Buffers.IMemoryOwner buffer; @@ -20,6 +20,7 @@ internal class ZipExrCompressor : ExrBaseCompressor { this.compressionLevel = compressionLevel; this.buffer = allocator.Allocate((int)bytesPerBlock); + this.memoryStream = new(); } /// diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index f8eb17f4e..6773f3b63 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -53,7 +53,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore : base(options.GeneralOptions) { this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs index b16c925f8..6aa3ebc70 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs @@ -19,17 +19,17 @@ public class ExrEncoderTests [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrEncoder_WithNoCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "NoCompression", compression: ExrCompression.None, imageDecoder: ExrDecoder.Instance); + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "NoCompression", compression: ExrCompression.None); [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrEncoder_WithZipCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipCompression", compression: ExrCompression.Zip, imageDecoder: ExrDecoder.Instance); + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipCompression", compression: ExrCompression.Zip); [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrEncoder_WithZipsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipsCompression", compression: ExrCompression.Zips, imageDecoder: ExrDecoder.Instance); + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipsCompression", compression: ExrCompression.Zips); protected static void TestExrEncoderCore( TestImageProvider provider, From d5f4e23f01bc847a14f027b9f7cf99f16ba8b036 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 25 Mar 2026 17:18:56 +0100 Subject: [PATCH 085/240] Avoid code duplication for CalculateBytesPerRow and RowsPerBlock --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 50 ++------------ src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 70 ++++---------------- src/ImageSharp/Formats/Exr/ExrUtils.cs | 41 ++++++++++++ 3 files changed, 58 insertions(+), 103 deletions(-) create mode 100644 src/ImageSharp/Formats/Exr/ExrUtils.cs diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 6773f3b63..44db67b61 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -132,8 +132,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); + uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; int width = this.Width; int height = this.Height; @@ -185,8 +185,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = this.CalculateBytesPerRow((uint)this.Width); - uint rowsPerBlock = this.RowsPerBlock(); + uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); + uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; int width = this.Width; int height = this.Height; @@ -728,48 +728,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return false; } - private uint CalculateBytesPerRow(uint width) - { - uint bytesPerRow = 0; - foreach (ExrChannelInfo channelInfo in this.Channels) - { - if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) - { - if (channelInfo.PixelType == ExrPixelType.Half) - { - bytesPerRow += 2 * width; - } - else - { - bytesPerRow += 4 * width; - } - } - } - - return bytesPerRow; - } - - private uint RowsPerBlock() - { - switch (this.Compression) - { - case ExrCompression.Zip: - case ExrCompression.Pxr24: - return 16; - case ExrCompression.B44: - case ExrCompression.B44A: - case ExrCompression.Piz: - return 32; - - default: - return 1; - } - } - private ulong ReadUnsignedLong(BufferedReadStream stream) { int bytesRead = stream.Read(this.buffer, 0, 8); diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index e393dcf68..2705a2057 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -158,8 +158,8 @@ internal sealed class ExrEncoderCore ExrCompression compression) where TPixel : unmanaged, IPixel { - uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width); - uint rowsPerBlock = this.RowsPerBlock(compression); + uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); + uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; int channelCount = channels.Count; @@ -234,8 +234,8 @@ internal sealed class ExrEncoderCore ExrCompression compression) where TPixel : unmanaged, IPixel { - uint bytesPerRow = this.CalculateBytesPerRow(channels, (uint)width); - uint rowsPerBlock = this.RowsPerBlock(compression); + uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); + uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; int channelCount = channels.Count; @@ -335,19 +335,19 @@ internal sealed class ExrEncoderCore int offset = 0; for (int x = 0; x < width; x++) { - this.WriteHalfSingle(buffer.Slice(offset), blueBuffer[x]); + WriteHalfSingle(buffer.Slice(offset), blueBuffer[x]); offset += 2; } for (int x = 0; x < width; x++) { - this.WriteHalfSingle(buffer.Slice(offset), greenBuffer[x]); + WriteHalfSingle(buffer.Slice(offset), greenBuffer[x]); offset += 2; } for (int x = 0; x < width; x++) { - this.WriteHalfSingle(buffer.Slice(offset), redBuffer[x]); + WriteHalfSingle(buffer.Slice(offset), redBuffer[x]); offset += 2; } } @@ -357,19 +357,19 @@ internal sealed class ExrEncoderCore int offset = 0; for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(buffer.Slice(offset), blueBuffer[x]); + WriteUnsignedInt(buffer.Slice(offset), blueBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(buffer.Slice(offset), greenBuffer[x]); + WriteUnsignedInt(buffer.Slice(offset), greenBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - this.WriteUnsignedInt(buffer.Slice(offset), redBuffer[x]); + WriteUnsignedInt(buffer.Slice(offset), redBuffer[x]); offset += 4; } } @@ -521,10 +521,10 @@ internal sealed class ExrEncoderCore private unsafe void WriteSingle(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); [MethodImpl(InliningOptions.ShortMethod)] - private void WriteHalfSingle(Span buffer, float value) + private static void WriteHalfSingle(Span buffer, float value) { ushort valueAsShort = HalfTypeHelper.Pack(value); - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, valueAsShort); + BinaryPrimitives.WriteUInt16LittleEndian(buffer, valueAsShort); } [MethodImpl(InliningOptions.ShortMethod)] @@ -535,49 +535,5 @@ internal sealed class ExrEncoderCore } [MethodImpl(InliningOptions.ShortMethod)] - private void WriteUnsignedInt(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); - - // TODO: avoid code duplication: This code is duplicate in the decoder. - private uint CalculateBytesPerRow(List channels, uint width) - { - uint bytesPerRow = 0; - foreach (ExrChannelInfo channelInfo in channels) - { - if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) - || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) - { - if (channelInfo.PixelType == ExrPixelType.Half) - { - bytesPerRow += 2 * width; - } - else - { - bytesPerRow += 4 * width; - } - } - } - - return bytesPerRow; - } - - // TODO: avoid code duplication: This code is duplicate in the decoder. - private uint RowsPerBlock(ExrCompression compression) - { - switch (compression) - { - case ExrCompression.Zip: - case ExrCompression.Pxr24: - return 16; - case ExrCompression.B44: - case ExrCompression.B44A: - case ExrCompression.Piz: - return 32; - - default: - return 1; - } - } + private static void WriteUnsignedInt(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); } diff --git a/src/ImageSharp/Formats/Exr/ExrUtils.cs b/src/ImageSharp/Formats/Exr/ExrUtils.cs new file mode 100644 index 000000000..4d172c6ef --- /dev/null +++ b/src/ImageSharp/Formats/Exr/ExrUtils.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr.Constants; + +namespace SixLabors.ImageSharp.Formats.Exr; + +internal static class ExrUtils +{ + public static uint CalculateBytesPerRow(IList channels, uint width) + { + uint bytesPerRow = 0; + foreach (ExrChannelInfo channelInfo in channels) + { + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) + || channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) + { + if (channelInfo.PixelType == ExrPixelType.Half) + { + bytesPerRow += 2 * width; + } + else + { + bytesPerRow += 4 * width; + } + } + } + + return bytesPerRow; + } + + public static uint RowsPerBlock(ExrCompression compression) => compression switch + { + ExrCompression.Zip or ExrCompression.Pxr24 => 16, + ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Piz => 32, + _ => 1, + }; +} From 7cc73d9c15d452029cad81865b777c0e670b37c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 25 Mar 2026 19:54:37 +0100 Subject: [PATCH 086/240] Fix issue decoding last row block with zip compression --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 14 +++++-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 43 ++++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 44db67b61..52a08e495 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -149,7 +149,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); - for (uint y = 0; y < height; y += rowsPerBlock) + int decodedRows = 0; + while (decodedRows < height) { ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; @@ -175,6 +176,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); } + + decodedRows++; } stream.Position = nextRowOffsetPosition; @@ -202,7 +205,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); - for (uint y = 0; y < height; y += rowsPerBlock) + int decodedRows = 0; + while (decodedRows < height) { ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; @@ -223,13 +227,15 @@ internal sealed class ExrDecoderCore : ImageDecoderCore offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } - stream.Position = nextRowOffsetPosition; - for (int x = 0; x < width; x++) { pixelRow[x] = ColorScaleTo32Bit(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); } + + decodedRows++; } + + stream.Position = nextRowOffsetPosition; } } diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 2705a2057..ff606b677 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -201,10 +201,10 @@ internal sealed class ExrEncoderCore switch (this.pixelType) { case ExrPixelType.Float: - this.WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); break; case ExrPixelType.Half: - this.WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); break; } @@ -277,7 +277,7 @@ internal sealed class ExrEncoderCore // Write row data to row block buffer. Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); - this.WriteUnsignedIntRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteUnsignedIntRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); rowsInBlockCount++; } @@ -308,68 +308,68 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } - private void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; for (int x = 0; x < width; x++) { - this.WriteSingle(buffer.Slice(offset), blueBuffer[x]); + WriteSingleToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - this.WriteSingle(buffer.Slice(offset), greenBuffer[x]); + WriteSingleToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - this.WriteSingle(buffer.Slice(offset), redBuffer[x]); + WriteSingleToBuffer(buffer.Slice(offset, 4), redBuffer[x]); offset += 4; } } - private void WriteHalfSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteHalfSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; for (int x = 0; x < width; x++) { - WriteHalfSingle(buffer.Slice(offset), blueBuffer[x]); + WriteHalfSingleToBuffer(buffer.Slice(offset, 2), blueBuffer[x]); offset += 2; } for (int x = 0; x < width; x++) { - WriteHalfSingle(buffer.Slice(offset), greenBuffer[x]); + WriteHalfSingleToBuffer(buffer.Slice(offset, 2), greenBuffer[x]); offset += 2; } for (int x = 0; x < width; x++) { - WriteHalfSingle(buffer.Slice(offset), redBuffer[x]); + WriteHalfSingleToBuffer(buffer.Slice(offset, 2), redBuffer[x]); offset += 2; } } - private void WriteUnsignedIntRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteUnsignedIntRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; for (int x = 0; x < width; x++) { - WriteUnsignedInt(buffer.Slice(offset), blueBuffer[x]); + WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - WriteUnsignedInt(buffer.Slice(offset), greenBuffer[x]); + WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); offset += 4; } for (int x = 0; x < width; x++) { - WriteUnsignedInt(buffer.Slice(offset), redBuffer[x]); + WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), redBuffer[x]); offset += 4; } } @@ -518,22 +518,15 @@ internal sealed class ExrEncoderCore } [MethodImpl(InliningOptions.ShortMethod)] - private unsafe void WriteSingle(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); + private static unsafe void WriteSingleToBuffer(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); [MethodImpl(InliningOptions.ShortMethod)] - private static void WriteHalfSingle(Span buffer, float value) + private static void WriteHalfSingleToBuffer(Span buffer, float value) { ushort valueAsShort = HalfTypeHelper.Pack(value); BinaryPrimitives.WriteUInt16LittleEndian(buffer, valueAsShort); } [MethodImpl(InliningOptions.ShortMethod)] - private void WriteUnsignedInt(Stream stream, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); - stream.Write(this.buffer.AsSpan(0, 4)); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static void WriteUnsignedInt(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + private static void WriteUnsignedIntToBuffer(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); } From 6a92b0cd442c34b220019ddda8928a9038dee7ab Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 26 Mar 2026 17:35:57 +0100 Subject: [PATCH 087/240] Implement missing Rgb96 methods --- .../Common/Helpers/ColorNumerics.cs | 11 ++ .../PixelImplementations/Rgb96.cs | 100 +++++++++++++----- .../PixelImplementations/Rgba32.cs | 13 +++ 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 1c30d857f..7a0e6d47a 100644 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs @@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp; /// internal static class ColorNumerics { + private const float Scale32Bit = 1f / 0xFFFFFFFF; + /// /// Vector for converting pixel to gray value as specified by /// ITU-R Recommendation BT.709. @@ -132,6 +134,15 @@ internal static class ColorNumerics // (V * 255 + 32895) >> 16 (byte)(((component * 255) + 32895) >> 16); + /// + /// Scales a value from an 32 bit to + /// an 8 bit equivalent. + /// + /// The 32 bit component value. + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte From32BitTo8Bit(uint component) => (byte)(component * Scale32Bit); + /// /// Scales a value from an 8 bit to /// an 16 bit equivalent. diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 165f13b07..6237d3d37 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// [StructLayout(LayoutKind.Sequential)] -public partial struct Rgb96 : IPixel +public partial struct Rgb96 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; @@ -74,60 +74,106 @@ public partial struct Rgb96 : IPixel /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( + public readonly Vector4 ToVector4() => new( this.R * InvMax, this.G * InvMax, this.B * InvMax, 1.0f); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + public static PixelOperations CreatePixelOperations() => new(); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public static Rgb96 FromScaledVector4(Vector4 source) => FromVector4(source); - /// - public override string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromVector4(Vector4 source) => FromVector4(source); - public static PixelOperations CreatePixelOperations() => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B); - public static Rgb96 FromScaledVector4(Vector4 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromArgb32(Argb32 source) => new(source.R, source.G, source.B); - public static Rgb96 FromVector4(Vector4 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - public static Rgb96 FromAbgr32(Abgr32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); - public static Rgb96 FromArgb32(Argb32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B); - public static Rgb96 FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromL8(L8 source) + { + ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); + return new Rgb96(rgb, rgb, rgb); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - public static Rgb96 FromBgr24(Bgr24 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromLa16(La16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); - public static Rgb96 FromBgra32(Bgra32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromLa32(La32 source) => new(source.L, source.L, source.L); - public static Rgb96 FromL8(L8 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); - public static Rgb96 FromL16(L16 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B); - public static Rgb96 FromLa16(La16 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B); - public static Rgb96 FromLa32(La32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B); - public static Rgb96 FromRgb24(Rgb24 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16), + PixelColorType.RGB, + PixelAlphaRepresentation.None); - public static Rgb96 FromRgba32(Rgba32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromRgb96(this); - public static Rgb96 FromRgb48(Rgb48 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - public static Rgb96 FromRgba64(Rgba64 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - public static PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); - public Rgba32 ToRgba32() => throw new NotImplementedException(); + /// + public override readonly bool Equals(object? obj) => obj is Rgb96 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 199754c69..34a7f6787 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -314,6 +314,19 @@ public partial struct Rgba32 : IPixel, IPackedVector A = ColorNumerics.From16BitTo8Bit(source.A) }; + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromRgb96(Rgb96 source) + => new() + { + R = ColorNumerics.From32BitTo8Bit(source.R), + G = ColorNumerics.From32BitTo8Bit(source.G), + B = ColorNumerics.From32BitTo8Bit(source.B) + }; + /// /// Converts the value of this instance to a hexadecimal string. /// From 355d1bc6b6c64c02ecb6d40f62aa23d97475dbd9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 26 Mar 2026 18:29:28 +0100 Subject: [PATCH 088/240] Implement missing methods for Rgba128 --- .../PixelImplementations/Rgb96.cs | 10 +-- .../PixelImplementations/Rgba128.cs | 87 ++++++++++++------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 6237d3d37..d318b067c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -155,7 +155,7 @@ public partial struct Rgb96 : IPixel, IEquatable /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 16, 16, 16), + PixelComponentInfo.Create(3, 32, 32, 32), PixelColorType.RGB, PixelAlphaRepresentation.None); @@ -167,13 +167,13 @@ public partial struct Rgb96 : IPixel, IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - /// public override readonly string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); /// public override readonly bool Equals(object? obj) => obj is Rgb96 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index fd094be59..c748456b8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// [StructLayout(LayoutKind.Sequential)] -public partial struct Rgba128 : IPixel +public partial struct Rgba128 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; @@ -81,58 +81,87 @@ public partial struct Rgba128 : IPixel /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( + public readonly Vector4 ToVector4() => new( this.R * InvMax, this.G * InvMax, this.B * InvMax, this.A * InvMax); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + /// + public static PixelOperations CreatePixelOperations() => new(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + /// + public static Rgba128 FromScaledVector4(Vector4 source) => FromVector4(source); - /// - public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + /// + public static Rgba128 FromVector4(Vector4 source) => FromVector4(source); - public static PixelOperations CreatePixelOperations() => throw new NotImplementedException(); + /// + public static Rgba128 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B, source.A); - public static Rgba128 FromScaledVector4(Vector4 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromArgb32(Argb32 source) => new(source.R, source.G, source.B, source.A); - public static Rgba128 FromVector4(Vector4 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); - public static Rgba128 FromAbgr32(Abgr32 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B, uint.MaxValue); - public static Rgba128 FromArgb32(Argb32 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B, source.A); - public static Rgba128 FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromL8(L8 source) + { + ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); + return new Rgba128(rgb, rgb, rgb, rgb); + } - public static Rgba128 FromBgr24(Bgr24 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue, source.PackedValue); - public static Rgba128 FromBgra32(Bgra32 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromLa16(La16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue, source.PackedValue); - public static Rgba128 FromL8(L8 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromLa32(La32 source) => new(source.L, source.L, source.L, source.L); - public static Rgba128 FromL16(L16 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B, uint.MaxValue); - public static Rgba128 FromLa16(La16 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B, source.A); - public static Rgba128 FromLa32(La32 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B, uint.MaxValue); - public static Rgba128 FromRgb24(Rgb24 source) => throw new NotImplementedException(); + /// + public static Rgba128 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B, source.A); - public static Rgba128 FromRgba32(Rgba32 source) => throw new NotImplementedException(); + /// + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 32, 32, 32), + PixelColorType.RGB, + PixelAlphaRepresentation.Unassociated); - public static Rgba128 FromRgb48(Rgb48 source) => throw new NotImplementedException(); + /// + public readonly Rgba32 ToRgba32() => throw new NotImplementedException(); - public static Rgba128 FromRgba64(Rgba64 source) => throw new NotImplementedException(); + /// + public readonly Vector4 ToScaledVector4() => throw new NotImplementedException(); - public static PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); - public Rgba32 ToRgba32() => throw new NotImplementedException(); + /// + public override readonly bool Equals(object? obj) => obj is Rgba128 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B && rgb.A == this.A; - public Vector4 ToScaledVector4() => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); } From c0e3d283d81ab4066b93ef74bcf3ce2bc3e25dea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 26 Mar 2026 19:18:21 +0100 Subject: [PATCH 089/240] Use cancellation token, when decoding exr images --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 14 +++++++++----- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 52a08e495..555b07bac 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -115,10 +115,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { case ExrPixelType.Half: case ExrPixelType.Float: - this.DecodeFloatingPointPixelData(stream, pixels); + this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken); break; case ExrPixelType.UnsignedInt: - this.DecodeUnsignedIntPixelData(stream, pixels); + this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken); break; default: ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); @@ -128,7 +128,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return image; } - private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels) + private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); @@ -168,7 +168,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - offset += ReadFloatChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + offset += ReadFloatChannelData(stream, channel, decompressedPixelData[offset..], redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } for (int x = 0; x < width; x++) @@ -181,10 +181,12 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } stream.Position = nextRowOffsetPosition; + + cancellationToken.ThrowIfCancellationRequested(); } } - private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels) + private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); @@ -236,6 +238,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } stream.Position = nextRowOffsetPosition; + + cancellationToken.ThrowIfCancellationRequested(); } } diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index ff606b677..d6736597a 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -241,7 +241,7 @@ internal sealed class ExrEncoderCore using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); - Span redBuffer = rgbBuffer.GetSpan().Slice(0, width); + Span redBuffer = rgbBuffer.GetSpan()[..width]; Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); From 3e7ef68dcaa16643e0db9d4738db6dc533331fbb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 26 Mar 2026 19:50:51 +0100 Subject: [PATCH 090/240] Use cancellation token when encoding exr images --- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index d6736597a..7a66dfac2 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -133,7 +133,7 @@ internal sealed class ExrEncoderCore case ExrPixelType.Half: case ExrPixelType.Float: { - ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression); + ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); stream.Position = (long)startOfRowOffsetData; this.WriteRowOffsets(stream, height, rowOffsets); break; @@ -141,7 +141,7 @@ internal sealed class ExrEncoderCore case ExrPixelType.UnsignedInt: { - ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression); + ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); stream.Position = (long)startOfRowOffsetData; this.WriteRowOffsets(stream, height, rowOffsets); break; @@ -155,7 +155,8 @@ internal sealed class ExrEncoderCore int width, int height, List channels, - ExrCompression compression) + ExrCompression compression, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); @@ -220,6 +221,8 @@ internal sealed class ExrEncoderCore stream.Position = pixelDataSizePos; stream.Write(this.buffer.AsSpan(0, 4)); stream.Position = positionAfterPixelData; + + cancellationToken.ThrowIfCancellationRequested(); } return rowOffsets; @@ -231,7 +234,8 @@ internal sealed class ExrEncoderCore int width, int height, List channels, - ExrCompression compression) + ExrCompression compression, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); @@ -290,6 +294,8 @@ internal sealed class ExrEncoderCore stream.Position = pixelDataSizePos; stream.Write(this.buffer.AsSpan(0, 4)); stream.Position = positionAfterPixelData; + + cancellationToken.ThrowIfCancellationRequested(); } return rowOffsets; From d29fe89a27e7f9faac9f5cd392e8fbd01d74ed72 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 10:21:02 +0100 Subject: [PATCH 091/240] Add benchmark for decoding exr image --- src/ImageSharp/Configuration.cs | 2 +- src/ImageSharp/Formats/Exr/ExrDecoder.cs | 2 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 126 +++--------------- src/ImageSharp/Formats/Exr/ExrMetadata.cs | 5 + .../PixelImplementations/Rgba32.cs | 1 + .../Codecs/Exr/DecodeExr.cs | 68 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Images/Input/Exr/Calliphora_benchmark.exr | 3 + 8 files changed, 96 insertions(+), 112 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs create mode 100644 tests/Images/Input/Exr/Calliphora_benchmark.exr diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 6e431ba31..a7f84363e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -5,10 +5,10 @@ using System.Collections.Concurrent; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; diff --git a/src/ImageSharp/Formats/Exr/ExrDecoder.cs b/src/ImageSharp/Formats/Exr/ExrDecoder.cs index 11937eed4..2e2771728 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoder.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoder.cs @@ -28,7 +28,7 @@ public class ExrDecoder : ImageDecoder return new ExrDecoderCore(new ExrDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } - //// + /// protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 555b07bac..4f3f11353 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; using System.Text; using SixLabors.ImageSharp.Formats.Exr.Compression; using SixLabors.ImageSharp.Formats.Exr.Constants; @@ -23,7 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Exr; internal sealed class ExrDecoderCore : ImageDecoderCore { private const float Scale32Bit = 1f / 0xFFFFFFFF; - private static readonly Vector4 Scale32BitVector = Vector128.Create(Scale32Bit, Scale32Bit, Scale32Bit, 1f).AsVector4(); /// /// Reusable buffer. @@ -56,11 +54,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.memoryAllocator = this.configuration.MemoryAllocator; } - /// - /// Gets the dimensions of the image. - /// - public Size Dimensions => new(this.Width, this.Height); - /// /// Gets or sets the image width. /// @@ -81,16 +74,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ExrCompression Compression { get; set; } - /// - /// Gets or sets the image data type, either RGB, RGBA or gray. - /// - private ExrImageDataType ImageDataType { get; set; } - - /// - /// Gets or sets the image type, either ScanLine or tiled. - /// - private ExrImageType ImageType { get; set; } - /// /// Gets or sets the header attributes. /// @@ -106,7 +89,6 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } ExrPixelType pixelType = this.ValidateChannels(); - this.ReadImageDataType(); Image image = new(this.configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -226,7 +208,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int channelIdx = 0; channelIdx < this.Channels.Count; channelIdx++) { ExrChannelInfo channel = this.Channels[channelIdx]; - offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData.Slice(offset), redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); + offset += this.ReadUnsignedIntChannelData(stream, channel, decompressedPixelData[offset..], redPixelData, greenPixelData, bluePixelData, alphaPixelData, width); } for (int x = 0; x < width; x++) @@ -320,29 +302,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } } - private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch { - switch (channel.PixelType) - { - case ExrPixelType.Half: - return ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width); - case ExrPixelType.Float: - return ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width); - } - - return 0; - } + ExrPixelType.Half => ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width), + ExrPixelType.Float => ReadPixelRowChannelSingle(decompressedPixelData, pixelData, width), + _ => 0, + }; - private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) + private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch { - switch (channel.PixelType) - { - case ExrPixelType.UnsignedInt: - return ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width); - } - - return 0; - } + ExrPixelType.UnsignedInt => ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width), + _ => 0, + }; private static int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) { @@ -442,7 +413,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore byte flagsByte2 = (byte)stream.ReadByte(); if ((flagsByte0 & (1 << 1)) != 0) { - this.ImageType = ExrImageType.Tiled; + ExrThrowHelper.ThrowNotSupported("Decoding tiled exr images is not supported yet!"); } this.HeaderAttributes = this.ParseHeaderAttributes(stream); @@ -560,7 +531,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore private List ReadChannelList(BufferedReadStream stream, int attributeSize) { - List channels = new(); + List channels = []; while (attributeSize > 1) { ExrChannelInfo channelInfo = this.ReadChannelInfo(stream, out int bytesRead); @@ -654,76 +625,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return pixelType.Value; } - private bool IsSupportedCompression() - { - switch (this.Compression) - { - case ExrCompression.None: - case ExrCompression.Zip: - case ExrCompression.Zips: - case ExrCompression.RunLengthEncoded: - case ExrCompression.B44: - return true; - } - - return false; - } - - private void ReadImageDataType() + private bool IsSupportedCompression() => this.Compression switch { - bool hasRedChannel = false; - bool hasGreenChannel = false; - bool hasBlueChannel = false; - bool hasAlphaChannel = false; - bool hasLuminance = false; - foreach (ExrChannelInfo channelInfo in this.Channels) - { - if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) - { - hasAlphaChannel = true; - } - - if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)) - { - hasRedChannel = true; - } - - if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)) - { - hasGreenChannel = true; - } - - if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)) - { - hasBlueChannel = true; - } - - if (channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) - { - hasLuminance = true; - } - } - - if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) - { - this.ImageDataType = ExrImageDataType.Rgba; - return; - } - - if (hasRedChannel && hasGreenChannel && hasBlueChannel) - { - this.ImageDataType = ExrImageDataType.Rgb; - return; - } - - if (hasLuminance && this.Channels.Count == 1) - { - this.ImageDataType = ExrImageDataType.Gray; - return; - } - - ExrThrowHelper.ThrowNotSupported("The image contains channels, which are not supported!"); - } + ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true, + _ => false, + }; private bool HasAlpha() { diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 552d2d113..32b9ef3c5 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -30,17 +30,22 @@ public class ExrMetadata : IFormatMetadata /// public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; + /// public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); + /// public void AfterImageApply(Image destination, Matrix4x4 matrix) where TPixel : unmanaged, IPixel => throw new NotImplementedException(); /// public IDeepCloneable DeepClone() => new ExrMetadata(this); + /// public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + /// public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); + /// ExrMetadata IDeepCloneable.DeepClone() => throw new NotImplementedException(); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 34a7f6787..491372447 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -318,6 +318,7 @@ public partial struct Rgba32 : IPixel, IPackedVector /// Initializes the pixel instance from an value. /// /// The value. + /// The pixel value as Rgba32. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba32 FromRgb96(Rgb96 source) => new() diff --git a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs new file mode 100644 index 000000000..87d68f40f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; + +using ImageMagick; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.Short))] +public class DecodeExr +{ + private Configuration configuration; + + private byte[] imageBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Exr.Benchamrk)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + this.configuration = Configuration.CreateDefaultInstance(); + new ExrConfigurationModule().Configure(this.configuration); + + this.imageBytes ??= File.ReadAllBytes(this.TestImageFullPath); + } + + [Benchmark(Description = "Magick Exr")] + public int ExrImageMagick() + { + MagickReadSettings settings = new() { Format = MagickFormat.Exr }; + using MemoryStream memoryStream = new(this.imageBytes); + using MagickImage image = new(memoryStream, settings); + return image.Width; + } + + [Benchmark(Description = "ImageSharp Exr")] + public int ExrImageSharp() + { + using MemoryStream memoryStream = new(this.imageBytes); + using Image image = Image.Load(memoryStream); + return image.Height; + } + + /* Results 27.03.2026 + BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2) + Intel Core i7-14700T 1.30GHz, 1 CPU, 28 logical and 20 physical cores + .NET SDK 10.0.201 + [Host] : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 + Job-VDWIGO : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 + + Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 + LaunchCount=1 WarmupCount=3 + + | Method | TestImage | Mean | Error | StdDev | Allocated | + |----------------- |----------------------------- |---------:|---------:|---------:|----------:| + | 'Magick Exr' | Exr/Calliphora_benchmark.exr | 20.37 ms | 0.790 ms | 0.043 ms | 12.98 KB | + | 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 45.68 ms | 4.999 ms | 0.274 ms | 34.09 KB | + */ +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1b0c0c865..8758b74f5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1383,6 +1383,7 @@ public static class TestImages public static class Exr { + public const string Benchamrk = "Exr/Calliphora_benchmark.exr"; public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; public const string UncompressedUintRgb = "Exr/rgb_uint32_uncompressed.exr"; diff --git a/tests/Images/Input/Exr/Calliphora_benchmark.exr b/tests/Images/Input/Exr/Calliphora_benchmark.exr new file mode 100644 index 000000000..da416b3c8 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_benchmark.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3368860692927e709365f2d37b92411068e77a0f23624ff57af6089ec69f357 +size 2592888 From 9ead3ebe3aa88a5a23355c18f214de656d12deb1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 13:05:31 +0100 Subject: [PATCH 092/240] Update Magick.NET-Q16 to version 14.11.1 for fix to decoding zip compressed exr files --- tests/Directory.Build.targets | 2 +- .../ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs | 2 +- .../ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs | 2 +- .../Codecs/Webp/DecodeWebp.cs | 4 ++-- .../LoadResizeSave/LoadResizeSaveStressRunner.cs | 4 ++-- .../ReferenceCodecs/MagickReferenceDecoder.cs | 14 +++++++------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 8c88ff647..6b25509ed 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -24,7 +24,7 @@ Do not update to 14+ yet. There's differnce in how the BMP decoder handles rounding in 16 bit images. See https://github.com/ImageMagick/ImageMagick/commit/27a0a9c37f18af9c8d823a3ea076f600843b553c --> - + diff --git a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs index 87d68f40f..76cd9c0d6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs @@ -34,7 +34,7 @@ public class DecodeExr } [Benchmark(Description = "Magick Exr")] - public int ExrImageMagick() + public uint ExrImageMagick() { MagickReadSettings settings = new() { Format = MagickFormat.Exr }; using MemoryStream memoryStream = new(this.imageBytes); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs index d6a6cf1fb..4c81aee6d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs @@ -27,7 +27,7 @@ public class DecodeTga => this.data = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "ImageMagick Tga")] - public int TgaImageMagick() + public uint TgaImageMagick() { MagickReadSettings settings = new() { Format = MagickFormat.Tga }; using MagickImage image = new(new MemoryStream(this.data), settings); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs index bba1bc187..a10f1527f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs @@ -42,7 +42,7 @@ public class DecodeWebp } [Benchmark(Description = "Magick Lossy Webp")] - public int WebpLossyMagick() + public uint WebpLossyMagick() { MagickReadSettings settings = new() { Format = MagickFormat.WebP }; using MemoryStream memoryStream = new(this.webpLossyBytes); @@ -59,7 +59,7 @@ public class DecodeWebp } [Benchmark(Description = "Magick Lossless Webp")] - public int WebpLosslessMagick() + public uint WebpLosslessMagick() { MagickReadSettings settings = new() { Format = MagickFormat.WebP }; diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index f8bf19d57..8835fdbcc 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -248,10 +248,10 @@ public class LoadResizeSaveStressRunner public void MagickResize(string input) { using MagickImage image = new(input); - this.LogImageProcessed(image.Width, image.Height); + this.LogImageProcessed((int)image.Width, (int)image.Height); // Resize it to fit a 150x150 square - image.Resize(this.ThumbnailSize, this.ThumbnailSize); + image.Resize((uint)this.ThumbnailSize, (uint)this.ThumbnailSize); // Reduce the size of the file image.Strip(); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index a63818562..d72556b0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -61,14 +61,14 @@ public class MagickReferenceDecoder : ImageDecoder MagickReadSettings settings = new() { - FrameCount = (int)options.MaxFrames + FrameCount = options.MaxFrames }; settings.SetDefines(bmpReadDefines); settings.SetDefines(pngReadDefines); using MagickImageCollection magickImageCollection = new(stream, settings); - int imageWidth = magickImageCollection.Max(x => x.Width); - int imageHeight = magickImageCollection.Max(x => x.Height); + int imageWidth = (int)magickImageCollection.Max(x => x.Width); + int imageHeight = (int)magickImageCollection.Max(x => x.Height); List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) @@ -77,10 +77,10 @@ public class MagickReferenceDecoder : ImageDecoder framesList.Add(frame); Buffer2DRegion buffer = frame.PixelBuffer.GetRegion( - imageWidth - magicFrame.Width, - imageHeight - magicFrame.Height, - magicFrame.Width, - magicFrame.Height); + (int)(imageWidth - magicFrame.Width), + (int)(imageHeight - magicFrame.Height), + (int)magicFrame.Width, + (int)magicFrame.Height); using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) From aa0af92faaf7bb5ad4b3365709271ffa9c51bb1c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 16:45:01 +0100 Subject: [PATCH 093/240] Use CompareToReferenceOutput for int pixel type --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 4 +++- ...d_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index beca93448..57abd0147 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -52,7 +52,9 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + + // Compare to referene output, since the reference decoder does not support this pixel type. + image.CompareToReferenceOutput(provider); } [Theory] diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png new file mode 100644 index 000000000..d27a842a0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15adaff9755ff38f0a2b30180ced9e52ac1bf51b8f3b80fd26d85f18c0eef686 +size 68918 From 53230af86bf5bf05e9a3d46cce6b704725823d2f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 17:20:43 +0100 Subject: [PATCH 094/240] Use tolerant comparer for B44 compression --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 57abd0147..fca8b6367 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests.Formats.Exr; @@ -42,7 +43,9 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + + // There is a 0,0059% difference to the Reference decoder. + image.CompareToOriginal(provider, ImageComparer.Tolerant(0.0005f), ReferenceDecoder); } [Theory] @@ -114,6 +117,8 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + + // Note: There is a 0,1190% difference to the reference decoder. + image.CompareToOriginal(provider, ImageComparer.Tolerant(0.011f), ReferenceDecoder); } } From a741e84a127dff39cc424f75afa1961a04176bfb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 17:23:17 +0100 Subject: [PATCH 095/240] Use exact comparer as default for exr decoder tests --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index fca8b6367..34cad442f 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -33,7 +33,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] @@ -67,7 +67,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] @@ -77,7 +77,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] @@ -87,7 +87,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] @@ -97,7 +97,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] @@ -107,7 +107,7 @@ public class ExrDecoderTests { using Image image = provider.GetImage(ExrDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } [Theory] From 8c0c85442cec2a1753335e897151109b625b02be Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Mar 2026 19:20:53 +0100 Subject: [PATCH 096/240] Set exr pixel type when decoding an image --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 18 +++++++++++++--- src/ImageSharp/Formats/Exr/ExrMetadata.cs | 14 ++++++------- .../Formats/Exr/ExrDecoderTests.cs | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 4f3f11353..9cff42a4e 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -43,6 +43,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ImageMetadata metadata; + /// + /// The exr specific metadata. + /// + private ExrMetadata exrMetadata; + /// /// Initializes a new instance of the class. /// @@ -74,6 +79,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ExrCompression Compression { get; set; } + /// + /// Gets or sets the pixel type. + /// + private ExrPixelType PixelType { get; set; } + /// /// Gets or sets the header attributes. /// @@ -88,12 +98,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } - ExrPixelType pixelType = this.ValidateChannels(); - Image image = new(this.configuration, this.Width, this.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); - switch (pixelType) + switch (this.PixelType) { case ExrPixelType.Half: case ExrPixelType.Float: @@ -422,9 +430,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Height = this.HeaderAttributes.DataWindow.YMax - this.HeaderAttributes.DataWindow.YMin + 1; this.Channels = this.HeaderAttributes.Channels; this.Compression = this.HeaderAttributes.Compression; + this.PixelType = this.ValidateChannels(); this.metadata = new ImageMetadata(); + this.exrMetadata = this.metadata.GetExrMetadata(); + this.exrMetadata.PixelType = this.PixelType; + return this.HeaderAttributes; } diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 32b9ef3c5..b32da8f03 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -28,24 +28,24 @@ public class ExrMetadata : IFormatMetadata /// /// Gets or sets the pixel format. /// - public ExrPixelType PixelType { get; set; } = ExrPixelType.Float; - - /// - public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); + public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; /// public void AfterImageApply(Image destination, Matrix4x4 matrix) where TPixel : unmanaged, IPixel => throw new NotImplementedException(); - /// - public IDeepCloneable DeepClone() => new ExrMetadata(this); - /// public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); /// public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); + /// + public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); + /// ExrMetadata IDeepCloneable.DeepClone() => throw new NotImplementedException(); + + /// + public IDeepCloneable DeepClone() => new ExrMetadata(this); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 34cad442f..0077523ad 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; @@ -21,19 +22,35 @@ public class ExrDecoderTests TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); ImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedHeight, imageInfo.Height); } + [Theory] + [InlineData(TestImages.Exr.Uncompressed)] + public void ExrDecoder_Identify_DetectsCorrectPixelType(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); + } + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Half(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); } [Theory] @@ -42,10 +59,12 @@ public class ExrDecoderTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); image.DebugSave(provider); // There is a 0,0059% difference to the Reference decoder. image.CompareToOriginal(provider, ImageComparer.Tolerant(0.0005f), ReferenceDecoder); + Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); } [Theory] @@ -54,10 +73,12 @@ public class ExrDecoderTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); image.DebugSave(provider); // Compare to referene output, since the reference decoder does not support this pixel type. image.CompareToReferenceOutput(provider); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); } [Theory] From c8c3656aaf767c6977cdd598738f2d3c418bcd41 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 28 Mar 2026 18:18:42 +0100 Subject: [PATCH 097/240] Implement GetPixelTypeInfo() for EXR --- .../Formats/Exr/Constants/ExrImageDataType.cs | 17 ++++- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 60 ++++++++++++++++++ src/ImageSharp/Formats/Exr/ExrMetadata.cs | 63 ++++++++++++++++++- .../Formats/Exr/ExrDecoderTests.cs | 28 ++++++++- 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs index d63a8e219..9453a7d9c 100644 --- a/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrImageDataType.cs @@ -3,13 +3,28 @@ namespace SixLabors.ImageSharp.Formats.Exr.Constants; -internal enum ExrImageDataType +/// +/// This enum represents the type of pixel data in the EXR image. +/// +public enum ExrImageDataType { + /// + /// The pixel data is unknown. + /// Unknown = 0, + /// + /// The pixel data has 3 channels: red, green and blue. + /// Rgb = 1, + /// + /// The pixel data has four channels: red, green, blue and a alpha channel. + /// Rgba = 2, + /// + /// There is only one channel with the luminance. + /// Gray = 3, } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 9cff42a4e..1127b146c 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -79,6 +79,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ExrCompression Compression { get; set; } + /// + /// Gets or sets the image data type, either RGB, RGBA or gray. + /// + private ExrImageDataType ImageDataType { get; set; } + /// /// Gets or sets the pixel type. /// @@ -403,6 +408,59 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return pixelType; } + private ExrImageDataType ReadImageDataType() + { + bool hasRedChannel = false; + bool hasGreenChannel = false; + bool hasBlueChannel = false; + bool hasAlphaChannel = false; + bool hasLuminance = false; + foreach (ExrChannelInfo channelInfo in this.Channels) + { + if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)) + { + hasAlphaChannel = true; + } + + if (channelInfo.ChannelName.Equals("R", StringComparison.Ordinal)) + { + hasRedChannel = true; + } + + if (channelInfo.ChannelName.Equals("G", StringComparison.Ordinal)) + { + hasGreenChannel = true; + } + + if (channelInfo.ChannelName.Equals("B", StringComparison.Ordinal)) + { + hasBlueChannel = true; + } + + if (channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) + { + hasLuminance = true; + } + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel && hasAlphaChannel) + { + return ExrImageDataType.Rgba; + } + + if (hasRedChannel && hasGreenChannel && hasBlueChannel) + { + return ExrImageDataType.Rgb; + } + + if (hasLuminance && this.Channels.Count == 1) + { + return ExrImageDataType.Gray; + } + + return ExrImageDataType.Unknown; + } + private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) { // Skip over the magick bytes, we already know its an EXR image. @@ -431,11 +489,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Channels = this.HeaderAttributes.Channels; this.Compression = this.HeaderAttributes.Compression; this.PixelType = this.ValidateChannels(); + this.ImageDataType = this.ReadImageDataType(); this.metadata = new ImageMetadata(); this.exrMetadata = this.metadata.GetExrMetadata(); this.exrMetadata.PixelType = this.PixelType; + this.exrMetadata.ImageDataType = this.ImageDataType; return this.HeaderAttributes; } diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index b32da8f03..7abba6da8 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -30,12 +30,69 @@ public class ExrMetadata : IFormatMetadata /// public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; + /// + /// Gets or sets the image data type, either RGB, RGBA or gray. + /// + public ExrImageDataType ImageDataType { get; set; } = ExrImageDataType.Unknown; + /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + public PixelTypeInfo GetPixelTypeInfo() + { + bool hasAlpha = this.ImageDataType is ExrImageDataType.Rgba; + + int bitsPerComponent = 32; + int bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; + if (this.PixelType == ExrPixelType.Half) + { + bitsPerComponent = 16; + bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; + } + + PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Unassociated : PixelAlphaRepresentation.None; + PixelColorType color = PixelColorType.RGB; + + int componentsCount = 0; + int[] precision = []; + switch (this.ImageDataType) + { + case ExrImageDataType.Rgb: + color = PixelColorType.RGB; + componentsCount = 3; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + precision[1] = bitsPerComponent; + precision[2] = bitsPerComponent; + break; + case ExrImageDataType.Rgba: + color = PixelColorType.RGB | PixelColorType.Alpha; + componentsCount = 4; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + precision[1] = bitsPerComponent; + precision[2] = bitsPerComponent; + precision[3] = bitsPerComponent; + break; + case ExrImageDataType.Gray: + color = PixelColorType.Luminance; + componentsCount = 1; + precision = new int[componentsCount]; + precision[0] = bitsPerComponent; + break; + } + + PixelComponentInfo info = PixelComponentInfo.Create(componentsCount, bitsPerPixel, precision); + return new PixelTypeInfo(bitsPerPixel) + { + AlphaRepresentation = alpha, + ComponentInfo = info, + ColorType = color + }; + } + /// - public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException(); + public void AfterImageApply(Image destination, Matrix4x4 matrix) + where TPixel : unmanaged, IPixel => throw new NotImplementedException(); /// public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 0077523ad..0d6ea213a 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -30,7 +30,20 @@ public class ExrDecoderTests [Theory] [InlineData(TestImages.Exr.Uncompressed)] - public void ExrDecoder_Identify_DetectsCorrectPixelType(string imagePath) + public void ExrDecoder_Identify_DetectsCorrectPixelType_Half(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); + } + + [Theory] + [InlineData(TestImages.Exr.UncompressedFloatRgb)] + public void ExrDecoder_Identify_DetectsCorrectPixelType_Float(string imagePath) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); @@ -41,6 +54,19 @@ public class ExrDecoderTests Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); } + [Theory] + [InlineData(TestImages.Exr.UncompressedUintRgb)] + public void ExrDecoder_Identify_DetectsCorrectPixelType_Int(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); + } + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Half(TestImageProvider provider) From b95663489eefebb91e7d91a1bfcb2c8c2b6ec8dc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Mar 2026 16:49:53 +0200 Subject: [PATCH 098/240] Add tests for exr metadata --- src/ImageSharp/Formats/Exr/ExrMetadata.cs | 19 +++-- .../Codecs/Exr/DecodeExr.cs | 2 +- .../Formats/Exr/ExrDecoderTests.cs | 54 +------------- .../Formats/Exr/ExrMetadataTests.cs | 70 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 +- .../Exr/Calliphora_uncompressed_rgba.exr | 3 + 6 files changed, 89 insertions(+), 62 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs create mode 100644 tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 7abba6da8..f618b6d6f 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -89,20 +89,25 @@ public class ExrMetadata : IFormatMetadata }; } - /// - public void AfterImageApply(Image destination, Matrix4x4 matrix) - where TPixel : unmanaged, IPixel => throw new NotImplementedException(); + public FormatConnectingMetadata ToFormatConnectingMetadata() => new() + { + EncodingType = EncodingType.Lossless, + PixelTypeInfo = this.GetPixelTypeInfo() + }; /// - public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException(); + public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => new() { PixelType = ExrPixelType.Half }; /// - public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException(); + ExrMetadata IDeepCloneable.DeepClone() => new(this); /// - ExrMetadata IDeepCloneable.DeepClone() => throw new NotImplementedException(); + public IDeepCloneable DeepClone() => new ExrMetadata(this); /// - public IDeepCloneable DeepClone() => new ExrMetadata(this); + public void AfterImageApply(Image destination, Matrix4x4 matrix) + where TPixel : unmanaged, IPixel + { + } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs index 76cd9c0d6..45021cadc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs @@ -21,7 +21,7 @@ public class DecodeExr private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Exr.Benchamrk)] + [Params(TestImages.Exr.Benchmark)] public string TestImage { get; set; } [GlobalSetup] diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index 0d6ea213a..e6a00c84d 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -15,61 +15,9 @@ public class ExrDecoderTests { private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; - [Theory] - [InlineData(TestImages.Exr.Uncompressed, 199, 297)] - public void ExrDecoder_Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - } - - [Theory] - [InlineData(TestImages.Exr.Uncompressed)] - public void ExrDecoder_Identify_DetectsCorrectPixelType_Half(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); - - Assert.NotNull(imageInfo); - Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); - } - - [Theory] - [InlineData(TestImages.Exr.UncompressedFloatRgb)] - public void ExrDecoder_Identify_DetectsCorrectPixelType_Float(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); - - Assert.NotNull(imageInfo); - Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); - } - - [Theory] - [InlineData(TestImages.Exr.UncompressedUintRgb)] - public void ExrDecoder_Identify_DetectsCorrectPixelType_Int(string imagePath) - { - TestFile testFile = TestFile.Create(imagePath); - using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(stream); - ExrMetadata exrMetaData = imageInfo.Metadata.GetExrMetadata(); - - Assert.NotNull(imageInfo); - Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); - } - [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] - public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Half(TestImageProvider provider) + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Half(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(ExrDecoder.Instance); diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs new file mode 100644 index 000000000..c94b6dce5 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +public class ExrMetadataTests +{ + [Fact] + public void CloneIsDeep() + { + ExrMetadata meta = new() + { ImageDataType = ExrImageDataType.Rgb, PixelType = ExrPixelType.Half }; + ExrMetadata clone = (ExrMetadata)meta.DeepClone(); + + clone.ImageDataType = ExrImageDataType.Gray; + clone.PixelType = ExrPixelType.Float; + + Assert.False(meta.ImageDataType.Equals(clone.ImageDataType)); + Assert.False(meta.PixelType.Equals(clone.PixelType)); + } + + [Theory] + [InlineData(TestImages.Exr.Uncompressed, 199, 297)] + public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } + + [Theory] + [InlineData(TestImages.Exr.Uncompressed, ExrPixelType.Half)] + [InlineData(TestImages.Exr.UncompressedFloatRgb, ExrPixelType.Float)] + [InlineData(TestImages.Exr.UncompressedUintRgb, ExrPixelType.UnsignedInt)] + public void Identify_DetectsCorrectPixelType(string imagePath, ExrPixelType expectedPixelType) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedPixelType, metadata.PixelType); + } + + [Theory] + [InlineData(TestImages.Exr.UncompressedRgba, ExrImageDataType.Rgba)] + [InlineData(TestImages.Exr.Rgb, ExrImageDataType.Rgb)] + [InlineData(TestImages.Exr.Gray, ExrImageDataType.Gray)] + public void Identify_DetectsCorrectImageDataType(string imagePath, ExrImageDataType expectedImageDataType) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedImageDataType, metadata.ImageDataType); + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8758b74f5..7cdc50d1e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1383,8 +1383,9 @@ public static class TestImages public static class Exr { - public const string Benchamrk = "Exr/Calliphora_benchmark.exr"; + public const string Benchmark = "Exr/Calliphora_benchmark.exr"; public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; + public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; public const string UncompressedUintRgb = "Exr/rgb_uint32_uncompressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; diff --git a/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr b/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr new file mode 100644 index 000000000..86bc1d354 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e9f6b5afa3c10c895ba67b51568295be40c2b1057224437600028487c581291 +size 481899 From a9f6dbc68b8f09d57b5ac2d9fa08f430a322db2f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Mar 2026 17:19:46 +0200 Subject: [PATCH 099/240] Add compression to exr metadata --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 1 + src/ImageSharp/Formats/Exr/ExrMetadata.cs | 7 ++++++- .../Formats/Exr/ExrMetadataTests.cs | 20 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 1127b146c..8a893c3c8 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -496,6 +496,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.exrMetadata = this.metadata.GetExrMetadata(); this.exrMetadata.PixelType = this.PixelType; this.exrMetadata.ImageDataType = this.ImageDataType; + this.exrMetadata.Compression = this.Compression; return this.HeaderAttributes; } diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index f618b6d6f..1fa724657 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -35,6 +35,11 @@ public class ExrMetadata : IFormatMetadata /// public ExrImageDataType ImageDataType { get; set; } = ExrImageDataType.Unknown; + /// + /// Gets or sets the compression method. + /// + public ExrCompression Compression { get; set; } = ExrCompression.None; + /// public PixelTypeInfo GetPixelTypeInfo() { @@ -92,7 +97,7 @@ public class ExrMetadata : IFormatMetadata /// public FormatConnectingMetadata ToFormatConnectingMetadata() => new() { - EncodingType = EncodingType.Lossless, + EncodingType = this.Compression is ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Pxr24 ? EncodingType.Lossy : EncodingType.Lossless, PixelTypeInfo = this.GetPixelTypeInfo() }; diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs index c94b6dce5..2cbbf0ec3 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -13,14 +13,16 @@ public class ExrMetadataTests public void CloneIsDeep() { ExrMetadata meta = new() - { ImageDataType = ExrImageDataType.Rgb, PixelType = ExrPixelType.Half }; + { ImageDataType = ExrImageDataType.Rgb, PixelType = ExrPixelType.Half, Compression = ExrCompression.None }; ExrMetadata clone = (ExrMetadata)meta.DeepClone(); clone.ImageDataType = ExrImageDataType.Gray; clone.PixelType = ExrPixelType.Float; + clone.Compression = ExrCompression.Zip; Assert.False(meta.ImageDataType.Equals(clone.ImageDataType)); Assert.False(meta.PixelType.Equals(clone.PixelType)); + Assert.False(meta.Compression.Equals(clone.Compression)); } [Theory] @@ -67,4 +69,20 @@ public class ExrMetadataTests Assert.NotNull(metadata); Assert.Equal(expectedImageDataType, metadata.ImageDataType); } + + [Theory] + [InlineData(TestImages.Exr.UncompressedRgba, ExrCompression.None)] + [InlineData(TestImages.Exr.B44, ExrCompression.B44)] + [InlineData(TestImages.Exr.Rle, ExrCompression.RunLengthEncoded)] + public void Identify_DetectsCorrectCompression(string imagePath, ExrCompression expectedCompression) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedCompression, metadata.Compression); + } } From 7939cc9514935239f9bac429a7770154673dd60f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Mar 2026 19:31:33 +0200 Subject: [PATCH 100/240] Throw InvalidImageContentException when requiered header fields are missing --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 8a893c3c8..126c4c8f6 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -562,6 +562,41 @@ internal sealed class ExrDecoderCore : ImageDecoderCore attribute = this.ReadAttribute(stream); } + if (!displayWindow.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the displayWindow attribute is missing!"); + } + + if (!dataWindow.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the dataWindow attribute is missing!"); + } + + if (channels is null) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the channels attribute is missing!"); + } + + if (!compression.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the compression attribute is missing!"); + } + + if (!lineOrder.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the lineOrder attribute is missing!"); + } + + if (!screenWindowWidth.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the screenWindowWidth attribute is missing!"); + } + + if (!screenWindowCenterX.HasValue || !screenWindowCenterY.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the screenWindowCenter attribute is missing!"); + } + ExrHeaderAttributes header = new( channels, compression.Value, From a1152c5dc3ccfe73ea18b40e5344e76ebb60e68b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 29 Mar 2026 19:57:03 +0200 Subject: [PATCH 101/240] Add some more comments --- .../Formats/Exr/ExrHeaderAttributes.cs | 37 +++++++++++++++++++ src/ImageSharp/Formats/Exr/ExrUtils.cs | 11 ++++++ .../Formats/Exr/IExrEncoderOptions.cs | 17 --------- 3 files changed, 48 insertions(+), 17 deletions(-) delete mode 100644 src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs diff --git a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index afda62bf9..cdcddd117 100644 --- a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -5,6 +5,10 @@ using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; +/// +/// The header of an EXR image. +/// +/// internal class ExrHeaderAttributes { public ExrHeaderAttributes( @@ -33,25 +37,58 @@ internal class ExrHeaderAttributes this.ChunkCount = chunkCount; } + /// + /// Gets or sets a description of the image channels stored in the file. + /// public IList Channels { get; set; } + /// + /// Gets or sets the compression method applied to the pixel data of all channels in the file. + /// public ExrCompression Compression { get; set; } + /// + /// Gets or sets the image’s data window. + /// public ExrBox2i DataWindow { get; set; } + /// + /// Gets or sets the image’s display window. + /// public ExrBox2i DisplayWindow { get; set; } + /// + /// Gets or sets in what order the scan lines in the file are stored in the file (increasing Y, decreasing Y, or, for tiled images, also random Y). + /// public ExrLineOrder LineOrder { get; set; } + /// + /// Gets or sets the aspect ratio of the image. + /// public float AspectRatio { get; set; } + /// + /// Gets or sets the screen width. + /// public float ScreenWindowWidth { get; set; } + /// + /// Gets or sets the screen window center. + /// public PointF ScreenWindowCenter { get; set; } + /// + /// Gets or sets the number of horizontal tiles. + /// public uint? TileXSize { get; set; } + /// + /// Gets or sets the number of vertical tiles. + /// public uint? TileYSize { get; set; } + /// + /// Gets or sets the chunk count. Indicates the number of chunks in this part. Required if the multipart bit (12) is set. + /// public int? ChunkCount { get; set; } } diff --git a/src/ImageSharp/Formats/Exr/ExrUtils.cs b/src/ImageSharp/Formats/Exr/ExrUtils.cs index 4d172c6ef..386210b81 100644 --- a/src/ImageSharp/Formats/Exr/ExrUtils.cs +++ b/src/ImageSharp/Formats/Exr/ExrUtils.cs @@ -7,6 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Exr; internal static class ExrUtils { + /// + /// Calcualtes the required bytes for a pixel row. + /// + /// The image channels array. + /// The width in pixels of a row. + /// The number of bytes per row. public static uint CalculateBytesPerRow(IList channels, uint width) { uint bytesPerRow = 0; @@ -32,6 +38,11 @@ internal static class ExrUtils return bytesPerRow; } + /// + /// Determines how many pixel rows there are in a block. This varies depending on the compression used. + /// + /// The compression used. + /// Pixel rows in a block. public static uint RowsPerBlock(ExrCompression compression) => compression switch { ExrCompression.Zip or ExrCompression.Pxr24 => 16, diff --git a/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs b/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs deleted file mode 100644 index 2ccf26ec1..000000000 --- a/src/ImageSharp/Formats/Exr/IExrEncoderOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Exr.Constants; - -namespace SixLabors.ImageSharp.Formats.Exr; - -/// -/// Configuration options for use during OpenExr encoding. -/// -internal interface IExrEncoderOptions -{ - /// - /// Gets the pixel type of the image. - /// - ExrPixelType? PixelType { get; } -} From 0c3ca3da2487527b9667eb24edb5431ae7902bab Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Mar 2026 10:39:32 +0200 Subject: [PATCH 102/240] Fix failing bmp tests --- src/ImageSharp/Formats/Exr/ExrBox2i.cs | 2 +- .../Formats/Bmp/BmpDecoderTests.cs | 35 +++++++++++++++++-- ...erted_Rgba32_RunLengthEncoded-inverted.png | 3 ++ ...EOL_MagickRefDecoder_Rgba32_pal8rlecut.png | 3 ++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png diff --git a/src/ImageSharp/Formats/Exr/ExrBox2i.cs b/src/ImageSharp/Formats/Exr/ExrBox2i.cs index 61da9b36d..032e60d92 100644 --- a/src/ImageSharp/Formats/Exr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/Exr/ExrBox2i.cs @@ -6,7 +6,7 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.Exr; [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] -internal struct ExrBox2i +internal readonly struct ExrBox2i { public ExrBox2i(int xMin, int yMin, int xMax, int yMax) { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index caa6c507d..3b9ab22ad 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -224,8 +224,8 @@ public class BmpDecoderTests } } + // An RLE-compressed image that uses “delta” codes, to skip over some pixels. [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] [WithFile(RLE8Delta, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -236,11 +236,21 @@ public class BmpDecoderTests image.CompareToOriginal(provider, MagickReferenceDecoder.Png); } + // An RLE-compressed image that uses “delta” codes, and early EOL & EOBMP markers, to skip over some pixels. + [Theory] + [WithFile(RLE8Cut, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder.Instance, options); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + [Theory] [WithFile(RLE8, PixelTypes.Rgba32, false)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] [WithFile(RLE8, PixelTypes.Rgba32, true)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : unmanaged, IPixel { @@ -255,6 +265,25 @@ public class BmpDecoderTests image.CompareToOriginal(provider, MagickReferenceDecoder.Png); } + [Theory] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel + { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder.Instance, options); + image.DebugSave(provider); + + // The Reference decoder does not support decoding compressed bmp which are inverted (with negative height). + image.CompareToReferenceOutput(provider); + } + [Theory] [WithFile(RLE24, PixelTypes.Rgba32, false)] [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png new file mode 100644 index 000000000..1c8828256 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e774cba4dda2fe9d3cdff141e7a8c1de7f3e9c8014093abf8697a34e6cc7144 +size 5379 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png new file mode 100644 index 000000000..86efdd207 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44d281c31cd264dcb812df8cb5dc9d5042e915d64e8013af5577dbfea6cbb1cf +size 3955 From e6a9980daf116f3ecbf30e6b3dea84f4f7b28129 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Mar 2026 10:43:41 +0200 Subject: [PATCH 103/240] Change expectedDefaultConfigurationCount to 12 --- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 3c6c759f8..bd6520084 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -20,7 +20,7 @@ public class ConfigurationTests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 11; + private readonly int expectedDefaultConfigurationCount = 12; public ConfigurationTests() { From 719ebb84b8c4687e88f66ab16623c08027ecef59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 10:24:31 +0000 Subject: [PATCH 104/240] Bump codecov/codecov-action from 5 to 6 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 16ae0317d..4ad43bb99 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -93,7 +93,7 @@ jobs: path: tests/Images/ActualOutput/ - name: Codecov Update - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests From 802e275e8e5b2815cfdee7369c4fbc400593e347 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Mar 2026 15:00:13 +0200 Subject: [PATCH 105/240] Use CompareToReferenceOutput for failing bmp tests on linux --- .../Formats/Bmp/BmpDecoderTests.cs | 19 ++++++++++++++----- ...der_CanDecodeBitfields_Rgba32_issue735.png | 3 +++ ...er_CanDecodeBitfields_Rgba32_rgb16-565.png | 3 +++ ...CanDecodeBitfields_Rgba32_rgb16-565pal.png | 3 +++ ...r_CanDecodeBitfields_Rgba32_rgb16bfdef.png | 3 +++ ...oder_CanDecodeBitfields_Rgba32_rgb32bf.png | 3 +++ ...r_CanDecodeBitfields_Rgba32_rgb32bfdef.png | 3 +++ ..._16Bit_Inverted_Rgba32_test16-inverted.png | 3 +++ ...coded_4Bit_WithDelta_Rgba32_pal4rlecut.png | 3 +++ ...oded_4Bit_WithDelta_Rgba32_pal4rletrns.png | 3 +++ ...it_WithDelta_Rgba32_rle4-delta-320x240.png | 3 +++ 11 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 3b9ab22ad..e85c6bcdf 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -91,13 +91,22 @@ public class BmpDecoderTests { using Image image = provider.GetImage(BmpDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToReferenceOutput(provider); } [Theory] [WithFile(Bit16Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_16Bit_Inverted(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + + [Theory] [WithFile(Bit8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) + public void BmpDecoder_CanDecode_8Bit_Inverted(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(BmpDecoder.Instance); @@ -156,7 +165,7 @@ public class BmpDecoderTests { using Image image = provider.GetImage(BmpDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance)); } [Theory] @@ -186,12 +195,12 @@ public class BmpDecoderTests public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) where TPixel : unmanaged, IPixel { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + RleSkippedPixelHandling skippedPixelHandling = RleSkippedPixelHandling.Black; BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; using Image image = provider.GetImage(BmpDecoder.Instance, options); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToReferenceOutput(provider); } [Theory] diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png new file mode 100644 index 000000000..48281cebc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15de93db72e2de0ad1a21b67931df57c1a129f062fe1ed3cb7f083761af4fe36 +size 61931 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png new file mode 100644 index 000000000..2e44b7ec5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 +size 9895 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png new file mode 100644 index 000000000..2e44b7ec5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 +size 9895 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png new file mode 100644 index 000000000..e91c987b0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c05e0b14a389d11f531d4238900106a81f25a741a55bbbcd160b8b67eb32adb6 +size 7019 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png new file mode 100644 index 000000000..71e2079d0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 +size 20640 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png new file mode 100644 index 000000000..71e2079d0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 +size 20640 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png new file mode 100644 index 000000000..7cf41fb5b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53a33da2c1a84c0e8426e1a2ba24e2fe2de17a7bc75760184fb10bf6dc96954a +size 7896 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png new file mode 100644 index 000000000..db7f30b41 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0c97446dec6af009423dcae431c44d23963179c3c3bfbeaf3f3b52022a33f68 +size 2414 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png new file mode 100644 index 000000000..c2959addd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a651b81764696cd4fe42113d5ac26006f54820d2df8b2103793e82c66597b2b0 +size 2837 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png new file mode 100644 index 000000000..0e52f5332 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55672d405a3d085c72cfc9fb26bc3955bf858e525c54c6c4505f093f011f378d +size 3188 From fccd5b55d99b8739de3262515313eba36d62ba49 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Mar 2026 17:23:40 +0200 Subject: [PATCH 106/240] Remove unused variables --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 40 +++++++------------- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 3 -- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 126c4c8f6..d69cd78e2 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -371,30 +371,9 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { ExrHeaderAttributes header = this.ReadExrHeader(stream); - int bitsPerPixel = this.CalculateBitsPerPixel(); - return new ImageInfo(new Size(header.DataWindow.XMax, header.DataWindow.YMax), this.metadata); } - private int CalculateBitsPerPixel() - { - int bitsPerPixel = 0; - for (int i = 0; i < this.Channels.Count; i++) - { - ExrChannelInfo channel = this.Channels[0]; - if (channel.PixelType is ExrPixelType.Float or ExrPixelType.UnsignedInt) - { - bitsPerPixel += 32; - } - else if (channel.PixelType == ExrPixelType.Half) - { - bitsPerPixel += 16; - } - } - - return bitsPerPixel; - } - private ExrPixelType ValidateChannels() { if (this.Channels.Count == 0) @@ -475,8 +454,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore // Next three bytes contain info's about the image. byte flagsByte0 = (byte)stream.ReadByte(); - byte flagsByte1 = (byte)stream.ReadByte(); - byte flagsByte2 = (byte)stream.ReadByte(); + stream.ReadByte(); + stream.ReadByte(); if ((flagsByte0 & (1 << 1)) != 0) { ExrThrowHelper.ThrowNotSupported("Decoding tiled exr images is not supported yet!"); @@ -587,6 +566,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the lineOrder attribute is missing!"); } + if (!aspectRatio.HasValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the aspectRatio attribute is missing!"); + } + if (!screenWindowWidth.HasValue) { ExrThrowHelper.ThrowInvalidImageContentException("Invalid exr image header, the screenWindowWidth attribute is missing!"); @@ -648,7 +632,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } // Last byte should be a null byte. - int byteRead = stream.ReadByte(); + stream.ReadByte(); return channels; } @@ -662,9 +646,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore bytesRead += 4; byte pLinear = (byte)stream.ReadByte(); - byte reserved0 = (byte)stream.ReadByte(); - byte reserved1 = (byte)stream.ReadByte(); - byte reserved2 = (byte)stream.ReadByte(); + + // Next 3 bytes are reserved bytes and not use. + stream.ReadByte(); + stream.ReadByte(); + stream.ReadByte(); bytesRead += 4; int xSampling = this.ReadSignedInteger(stream); diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 7a66dfac2..be2b7e5ff 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -122,8 +122,6 @@ internal sealed class ExrEncoderCore // Next is offsets table to each pixel row, which will be written after the pixel data was written. int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; - int numberOfChannels = 3; - uint rowSizeBytes = (uint)(width * numberOfChannels * bytesPerChannel); ulong startOfRowOffsetData = (ulong)stream.Position; stream.Position += 8 * height; @@ -162,7 +160,6 @@ internal sealed class ExrEncoderCore uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int channelCount = channels.Count; using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); From 84182c5aafa253f402be61816d3a1b8a3754d61c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 30 Mar 2026 17:35:25 +0200 Subject: [PATCH 107/240] Remove more unused variables --- .../Exr/Compression/Decompressors/B44ExrCompression.cs | 2 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index bf6d9929e..e5b735a39 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -18,7 +18,7 @@ internal class B44ExrCompression : ExrBaseDecompressor private readonly byte[] scratch = new byte[14]; - private ushort[] s = new ushort[16]; + private readonly ushort[] s = new ushort[16]; private readonly IMemoryOwner tmpBuffer; diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index be2b7e5ff..6f11bd9f4 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -121,7 +121,6 @@ internal sealed class ExrEncoderCore this.WriteHeader(stream, header); // Next is offsets table to each pixel row, which will be written after the pixel data was written. - int bytesPerChannel = this.pixelType == ExrPixelType.Half ? 2 : 4; ulong startOfRowOffsetData = (ulong)stream.Position; stream.Position += 8 * height; @@ -238,7 +237,6 @@ internal sealed class ExrEncoderCore uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; - int channelCount = channels.Count; using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); @@ -379,8 +377,6 @@ internal sealed class ExrEncoderCore private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets) { - ulong startOfRowOffsetData = (ulong)stream.Position; - ulong offset = startOfRowOffsetData; for (int i = 0; i < height; i++) { BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, rowOffsets[i]); From ac1905328d37213c92fcf57d32417f9322294fe1 Mon Sep 17 00:00:00 2001 From: Andreas Eriksson <4438107+andreas-eriksson@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:51:36 +0200 Subject: [PATCH 108/240] Fix Identify returning incorrect frame count for animated PNGs The Identify method had two bugs when processing fdAT (FrameData) chunks: 1. A spurious Skip(4) before SkipChunkDataAndCrc caused the stream to be misaligned by 4 bytes, since chunk.Length already includes the 4-byte sequence number. 2. Unlike Decode, which consumes all fdAT chunks for a frame in one shot via ReadScanlines + ReadNextFrameDataChunk, Identify processed them individually, calling InitializeFrameMetadata for each chunk and inflating the frame count. The fix removes the extra Skip(4) and adds SkipRemainingFrameDataChunks to consume all continuation fdAT chunks for a frame, mirroring how ReadNextFrameDataChunk works during decoding. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 30 +++++++++++++++++-- .../Formats/Png/PngDecoderTests.cs | 12 ++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../animated/issue-animated-frame-count.png | 3 ++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Png/animated/issue-animated-frame-count.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 896218267..52858ec12 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -428,9 +428,10 @@ internal sealed class PngDecoderCore : ImageDecoderCore InitializeFrameMetadata(framesMetadata, currentFrameControl.Value); - // Skip sequence number - this.currentStream.Skip(4); + // Skip data for this and all remaining FrameData chunks belonging to the same frame + // (comparable to how Decode consumes them via ReadScanlines + ReadNextFrameDataChunk). this.SkipChunkDataAndCrc(chunk); + this.SkipRemainingFrameDataChunks(buffer); break; case PngChunkType.Data: @@ -2093,6 +2094,31 @@ internal sealed class PngDecoderCore : ImageDecoderCore return 0; } + /// + /// Skips any remaining chunks belonging to the current frame. + /// This mirrors how is used during decoding: + /// consecutive fdAT chunks are consumed until a non-fdAT chunk is encountered, + /// which is stored in for the next iteration. + /// + /// Temporary buffer. + private void SkipRemainingFrameDataChunks(Span buffer) + { + while (this.TryReadChunk(buffer, out PngChunk chunk)) + { + if (chunk.Type is PngChunkType.FrameData) + { + chunk.Data?.Dispose(); + this.SkipChunkDataAndCrc(chunk); + } + else + { + // Not a FrameData chunk; store it so the next TryReadChunk call returns it. + this.nextChunk = chunk; + return; + } + } + } + /// /// Reads a chunk from the stream. /// diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index a58101a6b..69e656849 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -411,6 +411,18 @@ public partial class PngDecoderTests Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); } + [Fact] + public void Identify_AnimatedPng_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(50, imageInfo.FrameMetadataCollection.Count); + } + [Theory] [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fab1b2891..730e62d82 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -76,6 +76,7 @@ public static class TestImages public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png"; public const string FrameOffset = "Png/animated/frame-offset.png"; public const string DefaultNotAnimated = "Png/animated/default-not-animated.png"; + public const string AnimatedFrameCount = "Png/animated/issue-animated-frame-count.png"; public const string Issue2666 = "Png/issues/Issue_2666.png"; public const string Issue2882 = "Png/issues/Issue_2882.png"; diff --git a/tests/Images/Input/Png/animated/issue-animated-frame-count.png b/tests/Images/Input/Png/animated/issue-animated-frame-count.png new file mode 100644 index 000000000..db8ff47b9 --- /dev/null +++ b/tests/Images/Input/Png/animated/issue-animated-frame-count.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62d51679bcb096ae45ae0f5bf874916ad929014f68ae43b487253d5050c8b68b +size 13561079 From 0aeaaf77596cbf7a902524319bccbfc90b5f25ae Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Apr 2026 12:46:01 +0200 Subject: [PATCH 109/240] Move ReadSingle() to ExrDecoderCore --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 47 ++++++++++++++++--- .../Pbm/BufferedReadStreamExtensions.cs | 1 - src/ImageSharp/IO/BufferedReadStream.cs | 25 ---------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index d69cd78e2..1cf0e2730 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -516,14 +516,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore lineOrder = (ExrLineOrder)stream.ReadByte(); break; case ExrConstants.AttributeNames.PixelAspectRatio: - aspectRatio = stream.ReadSingle(this.buffer); + aspectRatio = this.ReadSingle(stream); break; case ExrConstants.AttributeNames.ScreenWindowCenter: - screenWindowCenterX = stream.ReadSingle(this.buffer); - screenWindowCenterY = stream.ReadSingle(this.buffer); + screenWindowCenterX = this.ReadSingle(stream); + screenWindowCenterY = this.ReadSingle(stream); break; case ExrConstants.AttributeNames.ScreenWindowWidth: - screenWindowWidth = stream.ReadSingle(this.buffer); + screenWindowWidth = this.ReadSingle(stream); break; case ExrConstants.AttributeNames.Tiles: tileXSize = this.ReadUnsignedInteger(stream); @@ -738,39 +738,72 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return false; } + /// + /// Reads a unsigned long value from the stream. + /// + /// The stream to read the data from. + /// The unsigned long value. private ulong ReadUnsignedLong(BufferedReadStream stream) { int bytesRead = stream.Read(this.buffer, 0, 8); if (bytesRead != 8) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a unsigned long from the stream!"); } return BinaryPrimitives.ReadUInt64LittleEndian(this.buffer); } + /// + /// Reads a unsigned integer value from the stream. + /// + /// The stream to read the data from. + /// The integer value. private uint ReadUnsignedInteger(BufferedReadStream stream) { int bytesRead = stream.Read(this.buffer, 0, 4); if (bytesRead != 4) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a unsigned int from the stream!"); } return BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); } + /// + /// Reads a signed integer value from the stream. + /// + /// The stream to read the data from. + /// The integer value. private int ReadSignedInteger(BufferedReadStream stream) { int bytesRead = stream.Read(this.buffer, 0, 4); if (bytesRead != 4) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a signed int from the stream!"); } return BinaryPrimitives.ReadInt32LittleEndian(this.buffer); } + /// + /// Reads a float value from the stream. + /// + /// The stream to read the data from. + /// The float value. + private float ReadSingle(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to read a float value from the stream!"); + } + + int intValue = BinaryPrimitives.ReadInt32BigEndian(this.buffer); + + return Unsafe.As(ref intValue); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(uint r, uint g, uint b, uint a) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index 3d5dde437..3b0e41a02 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm; diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 87a4d05bc..0372f677f 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -167,31 +167,6 @@ internal sealed class BufferedReadStream : Stream } } - /// - /// Reads a float value. - /// - /// The value. - public float ReadSingle(byte[] data) - { - int offset = 0; - int bytesToRead = 4; - while (bytesToRead > 0) - { - int bytesRead = this.Read(data, offset, bytesToRead); - if (bytesRead == 0) - { - throw new ImageFormatException("Not enough data to read a float value from the stream"); - } - - bytesToRead -= bytesRead; - offset += bytesRead; - } - - int intValue = BinaryPrimitives.ReadInt32BigEndian(data); - - return Unsafe.As(ref intValue); - } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int Read(byte[] buffer, int offset, int count) From 7b13e1df1b909510ba1303e4b7b25d9d1d8e9df4 Mon Sep 17 00:00:00 2001 From: Andreas Eriksson <4438107+andreas-eriksson@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:58:13 +0200 Subject: [PATCH 110/240] Add generated animated PNG tests for Identify and Decode frame counts --- .../Formats/Png/PngDecoderTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 69e656849..0ba886612 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -423,6 +423,55 @@ public partial class PngDecoderTests Assert.Equal(50, imageInfo.FrameMetadataCollection.Count); } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public void Identify_AnimatedPng_FrameCount_MatchesDecode(int frameCount) + { + using Image image = new(10, 10, Color.Red.ToPixel()); + for (int i = 1; i < frameCount; i++) + { + using ImageFrame frame = new(Configuration.Default, 10, 10); + image.Frames.AddFrame(frame); + } + + using MemoryStream stream = new(); + image.Save(stream, new PngEncoder()); + stream.Position = 0; + + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(frameCount, imageInfo.FrameMetadataCollection.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public void Decode_AnimatedPng_FrameCount(int frameCount) + { + using Image image = new(10, 10, Color.Red.ToPixel()); + for (int i = 1; i < frameCount; i++) + { + using ImageFrame frame = new(Configuration.Default, 10, 10); + image.Frames.AddFrame(frame); + } + + using MemoryStream stream = new(); + image.Save(stream, new PngEncoder()); + stream.Position = 0; + + using Image decoded = Image.Load(stream); + + Assert.Equal(frameCount, decoded.Frames.Count); + } + [Theory] [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) From b33f288a90e88160411d62553a40605b0ce2f842 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Apr 2026 22:42:17 +1000 Subject: [PATCH 111/240] Add working-buffer blending and adjust row APIs --- .../Advanced/IRowOperation{TBuffer}.cs | 4 +- .../Advanced/ParallelRowIterator.cs | 8 +- .../Formats/Webp/WebpAnimationDecoder.cs | 13 +- .../PixelFormats/PixelBlender{TPixel}.cs | 198 ++++++++++++++++-- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 16 +- 5 files changed, 208 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs index 8b46fc5c3..dfaec640b 100644 --- a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -15,12 +15,12 @@ public interface IRowOperation /// /// The bounds of the operation. /// The required buffer length. - int GetRequiredBufferLength(Rectangle bounds); + public int GetRequiredBufferLength(Rectangle bounds); /// /// Invokes the method passing the row and a buffer. /// /// The row y coordinate. /// The contiguous region of memory. - void Invoke(int y, Span span); + public void Invoke(int y, Span span); } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index b878f9ec0..d170631a2 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -68,7 +68,7 @@ public static partial class ParallelRowIterator ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); - Parallel.For( + _ = Parallel.For( 0, numOfSteps, parallelOptions, @@ -138,7 +138,7 @@ public static partial class ParallelRowIterator ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); - Parallel.For( + _ = Parallel.For( 0, numOfSteps, parallelOptions, @@ -195,7 +195,7 @@ public static partial class ParallelRowIterator ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); - Parallel.For( + _ = Parallel.For( 0, numOfSteps, parallelOptions, @@ -262,7 +262,7 @@ public static partial class ParallelRowIterator ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); - Parallel.For( + _ = Parallel.For( 0, numOfSteps, parallelOptions, diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index a23705413..71e609fbc 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Numerics; using SixLabors.ImageSharp.Formats.Webp.Chunks; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; @@ -456,15 +457,21 @@ internal class WebpAnimationDecoder : IDisposable // The destination frame has already been prepopulated with the pixel data from the previous frame // so blending will leave the desired result which takes into consideration restoration to the // background color within the restore area. - PixelBlender blender = - PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender( + PixelColorBlendingMode.Normal, + PixelAlphaCompositionMode.SrcOver); + + // By using a dedicated vector span we can avoid per-row pool allocations in PixelBlender.Blend + // We need 3 Vector4 values per pixel to store the background, foreground, and result pixels for blending. + using IMemoryOwner workingBufferOwner = imageFrame.Configuration.MemoryAllocator.Allocate(restoreArea.Width * 3); + Span workingBuffer = workingBufferOwner.GetSpan(); for (int y = 0; y < restoreArea.Height; y++) { Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); Span decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width]; - blender.Blend(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f); + blender.Blend(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f, workingBuffer); } return; diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index bad9ae01c..fcd28796f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Numerics; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats; @@ -52,16 +51,53 @@ public abstract class PixelBlender Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + 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.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } /// @@ -87,14 +123,48 @@ public abstract class PixelBlender Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends a row against a constant source color using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 2 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount, + Span workingBuffer) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } /// @@ -116,6 +186,27 @@ public abstract class PixelBlender ReadOnlySpan amount) => this.Blend(configuration, destination, background, source, amount); + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + Span workingBuffer) + => this.Blend(configuration, destination, background, source, amount, workingBuffer); + /// /// Blends 2 rows together /// @@ -142,20 +233,89 @@ public abstract class PixelBlender Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends a row against a constant source color. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + 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.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } /// - /// Blends a row against a constant source color. + /// Blends a row against a constant source color using caller-provided temporary vector scratch. /// /// to use internally /// the destination span @@ -165,26 +325,28 @@ public abstract class PixelBlender /// A span with values between 0 and 1 indicating the weight of the second source vector. /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. /// + /// Reusable temporary vector scratch with capacity for at least 2 rows. public void Blend( Configuration configuration, Span destination, ReadOnlySpan background, TPixel source, - ReadOnlySpan amount) + ReadOnlySpan amount, + Span workingBuffer) { int maxLength = destination.Length; Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); - using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } /// diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 7a1ffb85f..f2faee4f9 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -145,7 +146,7 @@ internal class DrawImageProcessor : ImageProcessor this.Blender, this.Opacity); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( configuration, new Rectangle(0, 0, foregroundRectangle.Width, foregroundRectangle.Height), in operation); @@ -161,7 +162,7 @@ internal class DrawImageProcessor : ImageProcessor /// /// A implementing the draw logic for . /// - private readonly struct RowOperation : IRowOperation + private readonly struct RowOperation : IRowOperation { private readonly Buffer2D background; private readonly Buffer2D foreground; @@ -190,13 +191,20 @@ internal class DrawImageProcessor : ImageProcessor this.opacity = opacity; } + /// + public int GetRequiredBufferLength(Rectangle bounds) + + // By using a dedicated vector span we can avoid per-row pool allocations in PixelBlender.Blend + // We need 3 Vector4 values per pixel to store the background, foreground, and result pixels for blending. + => 3 * bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(int y, Span span) { Span background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width); Span foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity, span); } } } From 1853289863a3b92c2e65d55258ebb9b7c6e6c4c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Apr 2026 18:35:52 +0200 Subject: [PATCH 112/240] Evaluate return value of stream.Read() to make sure enough data was read --- .../Decompressors/NoneExrCompression.cs | 8 ++++++- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 22 ++++++++++++++----- src/ImageSharp/IO/BufferedReadStream.cs | 1 - 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 26c69db8c..fb4fa5d83 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -15,7 +15,13 @@ internal class NoneExrCompression : ExrBaseDecompressor /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) - => stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); + { + int bytesRead = stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); + if (bytesRead != (int)this.BytesPerBlock) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data!"); + } + } /// protected override void Dispose(bool disposing) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 1cf0e2730..7598c62f3 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -454,13 +454,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore // Next three bytes contain info's about the image. byte flagsByte0 = (byte)stream.ReadByte(); - stream.ReadByte(); - stream.ReadByte(); if ((flagsByte0 & (1 << 1)) != 0) { ExrThrowHelper.ThrowNotSupported("Decoding tiled exr images is not supported yet!"); } + // Discard the next two bytes. + int bytesRead = stream.Read(this.buffer, 0, 2); + if (bytesRead != 2) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for exr file!"); + } + this.HeaderAttributes = this.ParseHeaderAttributes(stream); this.Width = this.HeaderAttributes.DataWindow.XMax - this.HeaderAttributes.DataWindow.XMin + 1; @@ -632,7 +637,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } // Last byte should be a null byte. - stream.ReadByte(); + if (stream.ReadByte() == -1) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read exr channel list!"); + } return channels; } @@ -648,9 +656,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore byte pLinear = (byte)stream.ReadByte(); // Next 3 bytes are reserved bytes and not use. - stream.ReadByte(); - stream.ReadByte(); - stream.ReadByte(); + if (stream.Read(this.buffer, 0, 3) != 3) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read exr channel info!"); + } + bytesRead += 4; int xSampling = this.ReadSignedInteger(stream); diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 0372f677f..8080aab87 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.Buffers.Binary; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.IO; From e06a015cf59148e24d1fc940aed3b9e4d8356103 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Apr 2026 11:39:29 +1000 Subject: [PATCH 113/240] Fix SIMD slicing and padding length handling. Fix #3104 --- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 14 ++++++++------ .../Encoder/SpectralConverter{TPixel}.cs | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index ff5ea5de3..076590605 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -752,7 +752,7 @@ internal static partial class SimdUtils /// Implementation is based on MagicScaler code: /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L80-L182 /// - internal static unsafe void ByteToNormalizedFloat( + internal static void ByteToNormalizedFloat( ReadOnlySpan source, Span destination) { @@ -1172,8 +1172,10 @@ internal static partial class SimdUtils Vector256 rgb, rg, bx; Vector256 r, g, b; + // Each iteration consumes 8 Rgb24 pixels (24 bytes) but starts with a 32-byte load, + // so we need 3 extra pixels of addressable slack beyond the vectorized chunk. const int bytesPerRgbStride = 24; - nuint count = (uint)source.Length / 8; + nuint count = source.Length > 3 ? (uint)(source.Length - 3) / 8 : 0; for (nuint i = 0; i < count; i++) { rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (uint)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); @@ -1193,10 +1195,10 @@ internal static partial class SimdUtils } int sliceCount = (int)(count * 8); - redChannel = redChannel.Slice(sliceCount); - greenChannel = greenChannel.Slice(sliceCount); - blueChannel = blueChannel.Slice(sliceCount); - source = source.Slice(sliceCount); + redChannel = redChannel[sliceCount..]; + greenChannel = greenChannel[sliceCount..]; + blueChannel = blueChannel[sliceCount..]; + source = source[sliceCount..]; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index b60ef68f1..8662c5c49 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -114,9 +114,9 @@ internal class SpectralConverter : SpectralConverter, IDisposable Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); - rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); - gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); - bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); + rLane.Slice(paddingStartIndex, paddedPixelsCount).Fill(rLane[paddingStartIndex - 1]); + gLane.Slice(paddingStartIndex, paddedPixelsCount).Fill(gLane[paddingStartIndex - 1]); + bLane.Slice(paddingStartIndex, paddedPixelsCount).Fill(bLane[paddingStartIndex - 1]); // Convert from rgb24 to target pixel type JpegColorConverterBase.ComponentValues values = new(this.componentProcessors, y); From a76c02f7c095df9b578a8c0336712b148abd92d1 Mon Sep 17 00:00:00 2001 From: Andreas <4438107+andreas-eriksson@users.noreply.github.com> Date: Tue, 7 Apr 2026 07:46:22 +0200 Subject: [PATCH 114/240] Replace test image with a smaller one. Adjusted Identify_AnimatedPng_ReadsFrameCountCorrectly to expect 48 frames instead of 50. --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 2 +- .../Images/Input/Png/animated/issue-animated-frame-count.png | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 0ba886612..802f2aba3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -420,7 +420,7 @@ public partial class PngDecoderTests ImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); - Assert.Equal(50, imageInfo.FrameMetadataCollection.Count); + Assert.Equal(48, imageInfo.FrameMetadataCollection.Count); } [Theory] diff --git a/tests/Images/Input/Png/animated/issue-animated-frame-count.png b/tests/Images/Input/Png/animated/issue-animated-frame-count.png index db8ff47b9..88427f487 100644 --- a/tests/Images/Input/Png/animated/issue-animated-frame-count.png +++ b/tests/Images/Input/Png/animated/issue-animated-frame-count.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62d51679bcb096ae45ae0f5bf874916ad929014f68ae43b487253d5050c8b68b -size 13561079 +oid sha256:af4e320f586ab26c55612a7ccfc98a8c99cd6a0efe8a70d379503751d06fe8bd +size 51542 From 9569449ac46e844c33c7b4073c8773d9f3d87134 Mon Sep 17 00:00:00 2001 From: Andreas <4438107+andreas-eriksson@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:19:40 +0200 Subject: [PATCH 115/240] Fix MaxFrames handling in PNG decoder - Change >= to > for correct MaxFrames boundary - Skip fdAT chunk data when hitting maxFrames in Identify to maintain stream alignment - Add tests for Identify and Load with MaxFrames --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 12 ++++++---- .../Formats/Png/PngDecoderTests.cs | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 52858ec12..d794c66e2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -214,7 +214,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; case PngChunkType.FrameData: { - if (frameCount >= this.maxFrames) + if (frameCount > this.maxFrames) { goto EOF; } @@ -275,7 +275,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore previousFrameControl = currentFrameControl; } - if (frameCount >= this.maxFrames) + if (frameCount > this.maxFrames) { goto EOF; } @@ -402,7 +402,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; case PngChunkType.FrameControl: ++frameCount; - if (frameCount >= this.maxFrames) + if (frameCount > this.maxFrames) { break; } @@ -411,8 +411,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; case PngChunkType.FrameData: - if (frameCount >= this.maxFrames) + if (frameCount > this.maxFrames) { + // Must skip the chunk data even when we've hit maxFrames, because TryReadChunk + // restores the stream position to the start of the fdAT data after CRC validation. + this.SkipChunkDataAndCrc(chunk); + this.SkipRemainingFrameDataChunks(buffer); break; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 802f2aba3..4712fc0dd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -423,6 +423,30 @@ public partial class PngDecoderTests Assert.Equal(48, imageInfo.FrameMetadataCollection.Count); } + [Fact] + public void Identify_AnimatedPngWithMaxFrames_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(new DecoderOptions { MaxFrames = 40 }, stream); + + Assert.NotNull(imageInfo); + Assert.Equal(40, imageInfo.FrameMetadataCollection.Count); + } + + [Fact] + public void Load_AnimatedPngWithMaxFrames_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + using Image image = Image.Load(new DecoderOptions { MaxFrames = 40 }, stream); + + Assert.NotNull(image); + Assert.Equal(40, image.Frames.Count); + } + [Theory] [InlineData(1)] [InlineData(2)] From 90f0c0b5d47bc3d68cc4ad69222658f497e29516 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Apr 2026 21:32:38 +1000 Subject: [PATCH 116/240] Update and simplify quantization color caches. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 - .../Quantization/ColorMatchingMode.cs | 10 +- .../EuclideanPixelMap{TPixel,TCache}.cs | 116 +++++- .../Quantization/IColorIndexCache.cs | 387 +++++------------- .../Quantization/OctreeQuantizer{TPixel}.cs | 2 +- .../Processing/ColorMatchingCaches.cs | 302 ++++++++++++++ .../Formats/Png/PngEncoderTests.cs | 2 +- .../Quantization/PaletteQuantizerTests.cs | 158 +++++++ 8 files changed, 667 insertions(+), 312 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index fef49bffd..2944b58e5 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -523,10 +523,8 @@ internal static class AotCompilerTools private static void AotCompilePixelMaps() where TPixel : unmanaged, IPixel { - default(EuclideanPixelMap).GetClosestColor(default, out _); default(EuclideanPixelMap).GetClosestColor(default, out _); default(EuclideanPixelMap).GetClosestColor(default, out _); - default(EuclideanPixelMap).GetClosestColor(default, out _); } /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs index 26fd7d5d7..c520d7c54 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs @@ -15,14 +15,8 @@ public enum ColorMatchingMode Coarse, /// - /// Enables an exact color match cache for the first 512 unique colors encountered, - /// falling back to coarse matching thereafter. - /// - Hybrid, - - /// - /// Performs exact color matching without any caching optimizations. - /// This is the slowest but most accurate matching strategy. + /// Performs exact color matching using a bounded exact-match cache with eviction. + /// This preserves exact color matching while accelerating repeated colors. /// Exact } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs index 5b0c7252c..e2e7206e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs @@ -3,6 +3,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -71,32 +73,107 @@ internal sealed class EuclideanPixelMap : PixelMap [MethodImpl(InliningOptions.ColdPath)] private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { - // Loop through the palette and find the nearest match. + ReadOnlySpan rgbaPalette = this.rgbaPalette; + ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPalette); int index = 0; - float leastDistance = float.MaxValue; - for (int i = 0; i < this.rgbaPalette.Length; i++) + int leastDistance = int.MaxValue; + int i = 0; + + if (Vector128.IsHardwareAccelerated && rgbaPalette.Length >= 4) { - Rgba32 candidate = this.rgbaPalette[i]; - if (candidate.PackedValue == rgba.PackedValue) - { - index = i; - break; - } + // Duplicate the query color so one 128-bit register can be subtracted from + // two packed RGBA candidates at a time after widening. + Vector128 pixel = Vector128.Create( + rgba.R, + rgba.G, + rgba.B, + rgba.A, + rgba.R, + rgba.G, + rgba.B, + rgba.A); - float distance = DistanceSquared(rgba, candidate); - if (distance == 0) + int vectorizedLength = rgbaPalette.Length & ~0x03; + + for (; i < vectorizedLength; i += 4) { - index = i; - break; + // Load four packed Rgba32 values (16 bytes) and widen them into two vectors: + // [c0.r, c0.g, c0.b, c0.a, c1.r, ...] and [c2.r, c2.g, c2.b, c2.a, c3.r, ...]. + Vector128 packed = Vector128.LoadUnsafe(ref Unsafe.As(ref Unsafe.Add(ref rgbaPaletteRef, i))); + Vector128 lowerDiff = Vector128.WidenLower(packed).AsInt16() - pixel; + Vector128 upperDiff = Vector128.WidenUpper(packed).AsInt16() - pixel; + + // MultiplyAddAdjacent collapses channel squares into RG + BA partial sums, + // so each pair of int lanes still corresponds to one candidate color. + Vector128 lowerPairs = Vector128_.MultiplyAddAdjacent(lowerDiff, lowerDiff); + Vector128 upperPairs = Vector128_.MultiplyAddAdjacent(upperDiff, upperDiff); + + // Sum the two partials for candidates i and i + 1. + ref int lowerRef = ref Unsafe.As, int>(ref lowerPairs); + int distance = lowerRef + Unsafe.Add(ref lowerRef, 1); + if (distance < leastDistance) + { + index = i; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + distance = Unsafe.Add(ref lowerRef, 2) + Unsafe.Add(ref lowerRef, 3); + if (distance < leastDistance) + { + index = i + 1; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + // Sum the two partials for candidates i + 2 and i + 3. + ref int upperRef = ref Unsafe.As, int>(ref upperPairs); + distance = upperRef + Unsafe.Add(ref upperRef, 1); + if (distance < leastDistance) + { + index = i + 2; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + distance = Unsafe.Add(ref upperRef, 2) + Unsafe.Add(ref upperRef, 3); + if (distance < leastDistance) + { + index = i + 3; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } } + } + for (; i < rgbaPalette.Length; i++) + { + int distance = DistanceSquared(rgba, Unsafe.Add(ref rgbaPaletteRef, i)); if (distance < leastDistance) { index = i; leastDistance = distance; + if (distance == 0) + { + goto Found; + } } } + Found: + // Now I have the index, pop it into the cache for next time _ = this.cache.TryAdd(rgba, (short)index); match = Unsafe.Add(ref paletteRef, (uint)index); @@ -111,12 +188,12 @@ internal sealed class EuclideanPixelMap : PixelMap /// The second point. /// The distance squared. [MethodImpl(InliningOptions.ShortMethod)] - private static float DistanceSquared(Rgba32 a, Rgba32 b) + private static int DistanceSquared(Rgba32 a, Rgba32 b) { - float deltaR = a.R - b.R; - float deltaG = a.G - b.G; - float deltaB = a.B - b.B; - float deltaA = a.A - b.A; + int deltaR = a.R - b.R; + int deltaG = a.G - b.G; + int deltaB = a.B - b.B; + int deltaA = a.A - b.A; return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); } @@ -177,8 +254,7 @@ internal static class PixelMapFactory ColorMatchingMode colorMatchingMode) where TPixel : unmanaged, IPixel => colorMatchingMode switch { - ColorMatchingMode.Hybrid => new EuclideanPixelMap(configuration, palette), - ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), + ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), _ => new EuclideanPixelMap(configuration, palette), }; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs index 32d95137b..76598e004 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -56,147 +56,6 @@ internal interface IColorIndexCache : IColorIndexCache public static abstract T Create(MemoryAllocator allocator); } -/// -/// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary -/// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. -/// -/// -/// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache -/// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket -/// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of -/// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. -/// -internal unsafe struct HybridCache : IColorIndexCache -{ - private CoarseCache coarseCache; - private AccurateCache accurateCache; - - public HybridCache(MemoryAllocator allocator) - { - this.accurateCache = AccurateCache.Create(allocator); - this.coarseCache = CoarseCache.Create(allocator); - } - - /// - public static HybridCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryAdd(Rgba32 color, short index) - { - if (this.accurateCache.TryAdd(color, index)) - { - return true; - } - - return this.coarseCache.TryAdd(color, index); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - if (this.accurateCache.TryGetValue(color, out value)) - { - return true; - } - - return this.coarseCache.TryGetValue(color, out value); - } - - /// - public readonly void Clear() - { - this.accurateCache.Clear(); - this.coarseCache.Clear(); - } - - /// - public void Dispose() - { - this.accurateCache.Dispose(); - this.coarseCache.Dispose(); - } -} - -/// -/// A coarse cache for color distance lookups that uses a fixed-size lookup table. -/// -/// -/// This cache uses a fixed lookup table with 2,097,152 bins, each storing a 2-byte value, -/// resulting in a memory usage of approximately 4 MB. Lookups and insertions are -/// performed in constant time (O(1)) via direct table indexing. This design is optimized for -/// speed while maintaining a predictable, fixed memory footprint. -/// -internal unsafe struct CoarseCache : IColorIndexCache -{ - private const int IndexRBits = 5; - private const int IndexGBits = 5; - private const int IndexBBits = 5; - private const int IndexABits = 6; - private const int IndexRCount = 1 << IndexRBits; // 32 bins for red - private const int IndexGCount = 1 << IndexGBits; // 32 bins for green - private const int IndexBCount = 1 << IndexBBits; // 32 bins for blue - private const int IndexACount = 1 << IndexABits; // 64 bins for alpha - private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; // 2,097,152 bins - - private readonly IMemoryOwner binsOwner; - private readonly short* binsPointer; - private MemoryHandle binsHandle; - - private CoarseCache(MemoryAllocator allocator) - { - this.binsOwner = allocator.Allocate(TotalBins); - this.binsOwner.GetSpan().Fill(-1); - this.binsHandle = this.binsOwner.Memory.Pin(); - this.binsPointer = (short*)this.binsHandle.Pointer; - } - - /// - public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryAdd(Rgba32 color, short value) - { - this.binsPointer[GetCoarseIndex(color)] = value; - return true; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - value = this.binsPointer[GetCoarseIndex(color)]; - return value > -1; // Coarse match found - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetCoarseIndex(Rgba32 color) - { - int rIndex = color.R >> (8 - IndexRBits); - int gIndex = color.G >> (8 - IndexGBits); - int bIndex = color.B >> (8 - IndexBBits); - int aIndex = color.A >> (8 - IndexABits); - - return (aIndex * IndexRCount * IndexGCount * IndexBCount) + - (rIndex * IndexGCount * IndexBCount) + - (gIndex * IndexBCount) + - bIndex; - } - - /// - public readonly void Clear() - => this.binsOwner.GetSpan().Fill(-1); - - /// - public void Dispose() - { - this.binsHandle.Dispose(); - this.binsOwner.Dispose(); - } -} - /// /// /// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, @@ -225,7 +84,7 @@ internal unsafe struct CoarseCache : IColorIndexCache /// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). /// /// -internal unsafe struct CoarseCacheLite : IColorIndexCache +internal unsafe struct CoarseCache : IColorIndexCache { // Use 5 bits per channel for R, G, and B: 32 levels each. // Total buckets = 32^3 = 32768. @@ -236,7 +95,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache private readonly AlphaBucket* buckets; private MemoryHandle bucketHandle; - private CoarseCacheLite(MemoryAllocator allocator) + private CoarseCache(MemoryAllocator allocator) { this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); this.bucketHandle = this.bucketsOwner.Memory.Pin(); @@ -244,7 +103,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } /// - public static CoarseCacheLite Create(MemoryAllocator allocator) => new(allocator); + public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); /// public readonly bool TryAdd(Rgba32 color, short paletteIndex) @@ -289,14 +148,11 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } [MethodImpl(InliningOptions.ShortMethod)] - private static byte QuantizeAlpha(byte a) - - // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. - => (byte)(a >> 2); + private static byte QuantizeAlpha(byte a) => (byte)(a >> 2); public struct AlphaEntry { - // Store the alpha value quantized to 6 bits (0..63) + // Store the alpha value quantized to 6 bits (0..63). public byte QuantizedAlpha; public short PaletteIndex; } @@ -312,7 +168,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache // 2. However, in practice (based on probability theory and typical image data), // the number of unique alpha values that actually occur for a given quantized RGB // bucket is usually very small. If you randomly sample 8 values out of 64, - // the probability that these 4 samples are all unique is high if the distribution + // the probability that these samples are all unique is high if the distribution // of alpha values is skewed or if only a few alpha values are used. // // 3. Statistically, for many real-world images, most RGB buckets will have only a couple @@ -377,51 +233,49 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } /// -/// A fixed-capacity dictionary with exactly 512 entries mapping a key -/// to a value. +/// A fixed-size exact-match cache that stores packed RGBA keys with 4-way set associativity. /// /// -/// The dictionary is implemented using a fixed array of 512 buckets and an entries array -/// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are -/// resolved through a linked chain stored in the field. +/// The cache holds 512 total entries split across 128 sets. Entries are evicted within a set +/// using round-robin replacement, but cached values are returned only when the full packed RGBA +/// key matches, preserving exact quantization results with predictable memory usage. /// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, -/// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are -/// typically very short; in the worst-case, the number of iterations is bounded by 256. +/// on average, O(1) since each lookup probes at most four candidate entries within the selected set. /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. /// internal unsafe struct AccurateCache : IColorIndexCache { - // Buckets array: each bucket holds the index (0-based) into the entries array - // of the first entry in the chain, or -1 if empty. - private readonly IMemoryOwner bucketsOwner; - private MemoryHandle bucketsHandle; - private short* buckets; + public const int Capacity = 512; + private const int Ways = 4; + private const int SetCount = Capacity / Ways; + private const int SetMask = SetCount - 1; - // Entries array: stores up to 256 entries. - private readonly IMemoryOwner entriesOwner; - private MemoryHandle entriesHandle; - private Entry* entries; + private readonly IMemoryOwner keysOwner; + private MemoryHandle keysHandle; + private uint* keys; - public const int Capacity = 512; + private readonly IMemoryOwner valuesOwner; + private MemoryHandle valuesHandle; + private ushort* values; + + private readonly IMemoryOwner nextVictimOwner; + private MemoryHandle nextVictimHandle; + private byte* nextVictim; private AccurateCache(MemoryAllocator allocator) { - this.Count = 0; - - // Allocate exactly 512 indexes for buckets. - this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.bucketsHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (short*)this.bucketsHandle.Pointer; - - // Allocate exactly 512 entries. - this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - this.entriesHandle = this.entriesOwner.Memory.Pin(); - this.entries = (Entry*)this.entriesHandle.Pointer; - } + this.keysOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.keysHandle = this.keysOwner.Memory.Pin(); + this.keys = (uint*)this.keysHandle.Pointer; - public int Count { get; private set; } + this.valuesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.valuesHandle = this.valuesOwner.Memory.Pin(); + this.values = (ushort*)this.valuesHandle.Pointer; + + this.nextVictimOwner = allocator.Allocate(SetCount, AllocationOptions.Clean); + this.nextVictimHandle = this.nextVictimOwner.Memory.Pin(); + this.nextVictim = (byte*)this.nextVictimHandle.Pointer; + } /// public static AccurateCache Create(MemoryAllocator allocator) => new(allocator); @@ -430,140 +284,113 @@ internal unsafe struct AccurateCache : IColorIndexCache [MethodImpl(InliningOptions.ShortMethod)] public bool TryAdd(Rgba32 color, short value) { - if (this.Count == Capacity) - { - return false; // Dictionary is full. - } - uint key = color.PackedValue; + int set = GetSetIndex(key); + int start = set * Ways; + int empty = -1; + + uint* keys = this.keys; + ushort* values = this.values; + ushort storedValue = (ushort)(value + 1); - // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A - // (with R in the most significant byte and A in the least significant). - // To compute the bucket index: - // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. - // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). - // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), - // which helps to counteract situations where one or more channels have a limited range. - // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, - // which corresponds to our fixed bucket count of 512. - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // Traverse the collision chain. - Entry* entries = this.entries; - while (i != -1) + for (int i = start; i < start + Ways; i++) { - Entry e = entries[i]; - if (e.Key == key) + ushort candidate = values[i]; + if (candidate == 0) { - // Key already exists; do not overwrite. - return false; + empty = i; + continue; } - i = e.Next; + if (keys[i] == key) + { + values[i] = storedValue; + return true; + } } - short index = (short)this.Count; - this.Count++; + int slot = empty >= 0 ? empty : start + this.nextVictim[set]; + keys[slot] = key; + values[slot] = storedValue; - // Insert the new entry: - entries[index].Key = key; - entries[index].Value = value; + if (empty < 0) + { + this.nextVictim[set] = (byte)((this.nextVictim[set] + 1) & (Ways - 1)); + } - // Link this new entry into the bucket chain. - entries[index].Next = this.buckets[bucket]; - this.buckets[bucket] = index; return true; } /// [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(Rgba32 color, out short value) + public readonly bool TryGetValue(Rgba32 color, out short value) { uint key = color.PackedValue; - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; + int start = GetSetIndex(key) * Ways; - // If the bucket is empty, return immediately. - if (i == -1) - { - value = -1; - return false; - } + uint* keys = this.keys; + ushort* values = this.values; - // Traverse the chain. - Entry* entries = this.entries; - do + for (int i = start; i < start + Ways; i++) { - Entry e = entries[i]; - if (e.Key == key) + ushort candidate = values[i]; + if (candidate != 0 && keys[i] == key) { - value = e.Value; + value = (short)(candidate - 1); return true; } - - i = e.Next; } - while (i != -1); value = -1; return false; } /// - /// Clears the dictionary. + /// Clears the cache. /// - public void Clear() + public readonly void Clear() { - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.Count = 0; + this.valuesOwner.GetSpan().Clear(); + this.nextVictimOwner.GetSpan().Clear(); } public void Dispose() { - this.bucketsHandle.Dispose(); - this.bucketsOwner.Dispose(); - this.entriesHandle.Dispose(); - this.entriesOwner.Dispose(); - this.buckets = null; - this.entries = null; + this.keysHandle.Dispose(); + this.keysOwner.Dispose(); + this.valuesHandle.Dispose(); + this.valuesOwner.Dispose(); + this.nextVictimHandle.Dispose(); + this.nextVictimOwner.Dispose(); + this.keys = null; + this.values = null; + this.nextVictim = null; } - private struct Entry - { - public uint Key; // The key (packed RGBA) - public short Value; // The value; -1 means unused. - public short Next; // Index of the next entry in the chain, or -1 if none. - } -} - -/// -/// Represents a cache that does not store any values. -/// It allows adding colors, but always returns false when trying to retrieve them. -/// -internal readonly struct NullCache : IColorIndexCache -{ - /// - public static NullCache Create(MemoryAllocator allocator) => default; - - /// - public bool TryAdd(Rgba32 color, short value) => true; - - /// - public bool TryGetValue(Rgba32 color, out short value) - { - value = -1; - return false; - } - - /// - public void Clear() - { - } - - /// - public void Dispose() - { - } + /// + /// Maps a packed RGBA key to one of the cache sets used by . + /// + /// The packed key. + /// The zero-based set index for the key. + /// + /// + /// The cache is 4-way set-associative, so this hash only needs to choose one of + /// sets before probing up to four candidate entries. + /// + /// + /// is laid out as R | (G << 8) | (B << 16) | (A << 24). + /// The XOR-fold mixes neighboring bytes into the low bits, and the final mask selects the + /// set. With the current 128-set layout that makes the selected set effectively depend on + /// the low 7 bits of R ^ G ^ B. Alpha still participates in the later exact key + /// comparison, but not in set selection. + /// + /// + /// Collisions are expected and acceptable here. Correctness comes from the full packed-key + /// comparison during probing; this hash only aims to spread keys cheaply enough that each + /// access touches at most one 4-entry set. + /// + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetSetIndex(uint key) + => (int)(((key >> 16) ^ (key >> 8) ^ key) & SetMask); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 07596b68a..bdf2ba20a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -368,7 +368,7 @@ public struct OctreeQuantizer : IQuantizer public void Dispose() => this.nodesOwner.Dispose(); [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OctreeNode + internal struct OctreeNode { public bool Leaf; public int PixelCount; diff --git a/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs b/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs new file mode 100644 index 000000000..dbaf21a8e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs @@ -0,0 +1,302 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.Standard))] +public class ColorMatchingCaches +{ + // IterationSetup forces BenchmarkDotNet to use a single benchmark invocation per iteration. + // Repeated lookups can safely replay a smaller working set because that workload is explicitly + // meant to model steady-state cache hits after warmup. + private const int RepeatedLookupCount = 262_144; + + // DitherLike should avoid replaying the same stream across multiple passes because that warms + // the caches in a way real high-churn input usually does not. Make the single pass larger instead. + private const int DitherLikeLookupCount = 1_048_576; + private const int RepeatedPassCount = 16; + + private Rgba32[] palette; + private Rgba32[] repeatedSeedColors; + private Rgba32[] repeatedLookups; + private Rgba32[] ditherLookups; + + private PixelMap coarse; + private PixelMap legacyCoarse; + private PixelMap exact; + private PixelMap uncached; + + [Params(16, 256)] + public int PaletteSize { get; set; } + + [Params(CacheWorkload.Repeated, CacheWorkload.DitherLike)] + public CacheWorkload Workload { get; set; } + + [GlobalSetup] + public void Setup() + { + this.palette = CreatePalette(this.PaletteSize); + this.repeatedSeedColors = CreateRepeatedSeedColors(this.palette); + this.repeatedLookups = CreateRepeatedLookups(this.repeatedSeedColors); + this.ditherLookups = CreateDitherLikeLookups(); + + this.coarse = CreatePixelMap(this.palette); + this.legacyCoarse = CreatePixelMap(this.palette); + this.exact = CreatePixelMap(this.palette); + this.uncached = CreatePixelMap(this.palette); + } + + [IterationSetup] + public void ResetCaches() + { + // Each benchmark iteration should start from the same cache state so we measure + // the cache policy itself rather than warm state carried over from a previous iteration. + this.coarse.Clear(this.palette); + this.legacyCoarse.Clear(this.palette); + this.exact.Clear(this.palette); + this.uncached.Clear(this.palette); + + if (this.Workload == CacheWorkload.Repeated) + { + // Prime the repeated workload so the benchmark reflects steady-state hit behavior + // instead of mostly measuring the first-wave fill cost. + Prime(this.coarse, this.repeatedSeedColors); + Prime(this.legacyCoarse, this.repeatedSeedColors); + Prime(this.exact, this.repeatedSeedColors); + Prime(this.uncached, this.repeatedSeedColors); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.coarse.Dispose(); + this.legacyCoarse.Dispose(); + this.exact.Dispose(); + this.uncached.Dispose(); + } + + [Benchmark(Baseline = true, Description = "Coarse")] + public int Coarse() => this.Run(this.coarse); + + [Benchmark(Description = "Legacy Coarse")] + public int LegacyCoarse() => this.Run(this.legacyCoarse); + + [Benchmark(Description = "Exact Cached")] + public int Exact() => this.Run(this.exact); + + [Benchmark(Description = "Exact Uncached")] + public int Uncached() => this.Run(this.uncached); + + public enum CacheWorkload + { + // A small working set that is intentionally reused after priming to measure hit-heavy behavior. + Repeated, + + // A deterministic high-churn stream intended to resemble dithered lookups where exact repeats are rare. + DitherLike + } + + private int Run(PixelMap map) + { + Rgba32[] lookups = this.Workload == CacheWorkload.Repeated ? this.repeatedLookups : this.ditherLookups; + int passCount = this.Workload == CacheWorkload.Repeated ? RepeatedPassCount : 1; + int checksum = 0; + + // Repeated intentionally replays the same lookup stream to measure steady-state hit behavior. + // DitherLike runs as a single larger pass so we do not turn a churn-heavy workload into an + // artificially warmed cache benchmark by replaying the exact same sequence. + for (int pass = 0; pass < passCount; pass++) + { + for (int i = 0; i < lookups.Length; i++) + { + checksum = unchecked((checksum * 31) + map.GetClosestColor(lookups[i], out _)); + } + } + + return checksum; + } + + private static PixelMap CreatePixelMap(Rgba32[] palette) + where TCache : struct, IColorIndexCache + => new EuclideanPixelMap(Configuration.Default, palette); + + private static void Prime(PixelMap map, Rgba32[] colors) + { + for (int i = 0; i < colors.Length; i++) + { + map.GetClosestColor(colors[i], out _); + } + } + + private static Rgba32[] CreatePalette(int count) + { + Rgba32[] result = new Rgba32[count]; + + for (int i = 0; i < result.Length; i++) + { + // Use the Knuth/golden-ratio multiplicative hash constant to spread colors across + // RGBA space without clustering into a gradient. That keeps the benchmark from + // accidentally favoring any cache because the palette itself is too regular. + uint value = unchecked((uint)(i + 1) * 2654435761U); + result[i] = new( + (byte)value, + (byte)(value >> 8), + (byte)(value >> 16), + (byte)((value >> 24) | 0x80)); + } + + return result; + } + + private static Rgba32[] CreateRepeatedSeedColors(Rgba32[] palette) + { + // Reuse colors derived from the palette but perturb them slightly so the workload still + // exercises nearest-color matching rather than only exact palette-entry hits. + int count = Math.Min(64, palette.Length * 2); + Rgba32[] result = new Rgba32[count]; + + for (int i = 0; i < result.Length; i++) + { + Rgba32 source = palette[(i * 17) % palette.Length]; + result[i] = new( + (byte)(source.R + ((i * 3) & 0x07)), + (byte)(source.G + ((i * 5) & 0x07)), + (byte)(source.B + ((i * 7) & 0x07)), + source.A); + } + + return result; + } + + private static Rgba32[] CreateRepeatedLookups(Rgba32[] seedColors) + { + Rgba32[] result = new Rgba32[RepeatedLookupCount]; + + // Cycle a small seed set to produce a stable, hit-heavy stream after priming. + for (int i = 0; i < result.Length; i++) + { + result[i] = seedColors[i % seedColors.Length]; + } + + return result; + } + + private static Rgba32[] CreateDitherLikeLookups() + { + Rgba32[] result = new Rgba32[DitherLikeLookupCount]; + + // Generate a deterministic pseudo-image signal with independent channel slopes so nearby + // samples are correlated but exact repeats are uncommon, which is closer to dithered input. + for (int i = 0; i < result.Length; i++) + { + int x = i & 511; + int y = i >> 9; + + result[i] = new( + (byte)((x * 17) + (y * 13)), + (byte)((x * 29) + (y * 7)), + (byte)((x * 11) + (y * 23)), + (byte)(255 - ((x * 3) + (y * 5)))); + } + + return result; + } + + /// + /// Preserves the original direct-mapped coarse cache implementation for side-by-side benchmarks. + /// + private unsafe struct LegacyCoarseCache : IColorIndexCache + { + private const int IndexRBits = 5; + private const int IndexGBits = 5; + private const int IndexBBits = 5; + private const int IndexABits = 6; + private const int IndexRCount = 1 << IndexRBits; + private const int IndexGCount = 1 << IndexGBits; + private const int IndexBCount = 1 << IndexBBits; + private const int IndexACount = 1 << IndexABits; + private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; + + private readonly IMemoryOwner binsOwner; + private readonly short* binsPointer; + private MemoryHandle binsHandle; + + private LegacyCoarseCache(MemoryAllocator allocator) + { + this.binsOwner = allocator.Allocate(TotalBins); + this.binsOwner.GetSpan().Fill(-1); + this.binsHandle = this.binsOwner.Memory.Pin(); + this.binsPointer = (short*)this.binsHandle.Pointer; + } + + public static LegacyCoarseCache Create(MemoryAllocator allocator) => new(allocator); + + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryAdd(Rgba32 color, short value) + { + this.binsPointer[GetCoarseIndex(color)] = value; + return true; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryGetValue(Rgba32 color, out short value) + { + value = this.binsPointer[GetCoarseIndex(color)]; + return value > -1; + } + + public readonly void Clear() => this.binsOwner.GetSpan().Fill(-1); + + public void Dispose() + { + this.binsHandle.Dispose(); + this.binsOwner.Dispose(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetCoarseIndex(Rgba32 color) + { + int rIndex = color.R >> (8 - IndexRBits); + int gIndex = color.G >> (8 - IndexGBits); + int bIndex = color.B >> (8 - IndexBBits); + int aIndex = color.A >> (8 - IndexABits); + + return (aIndex * IndexRCount * IndexGCount * IndexBCount) + + (rIndex * IndexGCount * IndexBCount) + + (gIndex * IndexBCount) + + bIndex; + } + } + + /// + /// Preserves the uncached path for exact-cache comparison benchmarks. + /// + private readonly struct UncachedCache : IColorIndexCache + { + public static UncachedCache Create(MemoryAllocator allocator) => default; + + public bool TryAdd(Rgba32 color, short value) => true; + + public bool TryGetValue(Rgba32 color, out short value) + { + value = -1; + return false; + } + + public void Clear() + { + } + + public void Dispose() + { + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 4ebcbc13b..eef8d5ba8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -680,7 +680,7 @@ public partial class PngEncoderTests PaletteQuantizer quantizer = new( palette.Select(Color.FromPixel).ToArray(), - new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Hybrid }); + new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Exact }); using MemoryStream ms = new(); image.Save(ms, new PngEncoder diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2a4b079b..07e9a4b0d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -75,4 +76,161 @@ public class PaletteQuantizerTests IQuantizer quantizer = KnownQuantizers.Werner; Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } + + [Fact] + public void ExactColorMatchingMatchesUncachedAfterCacheOverflow() + { + Rgba32[] palette = + [ + new Rgba32(0, 0, 0), + new Rgba32(7, 0, 0) + ]; + + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int i = 0; i < AccurateCache.Capacity; i++) + { + cachedExact.GetClosestColor(CreateOverflowFillerColor(i), out _); + } + + Rgba32 first = new(1, 0, 0); + Rgba32 second = new(6, 0, 0); + + AssertMatchesUncached(exact, cachedExact, first); + AssertMatchesUncached(exact, cachedExact, second); + } + + [Fact] + public void ExactColorMatchingMatchesUncachedAcrossManyProbeBinsAfterRepeatedEviction() + { + Rgba32[] palette = CreateGrayscalePalette(256); + + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int i = 0; i < AccurateCache.Capacity * 2; i++) + { + cachedExact.GetClosestColor(CreateEvictionFillerColor(i), out _); + } + + for (int i = 0; i < AccurateCache.Capacity; i++) + { + AssertMatchesUncached(exact, cachedExact, CreateEvictionProbeColor(i)); + } + } + + [Fact] + public void ExactColorMatchingMatchesUncachedForDitherStressColorSequence() + { + Rgba32[] palette = CreateGrayscalePalette(16); + + using Image source = CreateDitherStressImage(); + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < source.Width; x++) + { + AssertMatchesUncached(exact, cachedExact, source[x, y]); + } + } + } + + // Split the first 512 integers across R and G so the warmup loop produces 512 distinct exact colors: + // the low 8 bits go into R, and the ninth bit spills into G once R wraps after 255. + // Keeping B fixed and G offset away from zero also avoids accidentally probing the red-axis test colors below. + private static Rgba32 CreateOverflowFillerColor(int i) + => new((byte)i, (byte)(16 + (i >> 8)), 32); + + // Treat i as three packed 5-bit coordinates and expand each coordinate back to an 8-bit channel by + // shifting left by 3. That lands on the lower edge of each 5-bit coarse bucket, giving the test a + // deterministic way to fill many distinct coarse buckets before probing nearby exact colors. + private static Rgba32 CreateEvictionFillerColor(int i) + { + byte r = (byte)((i & 31) << 3); + byte g = (byte)(((i >> 5) & 31) << 3); + byte b = (byte)(((i >> 10) & 31) << 3); + return new(r, g, b); + } + + // Reconstruct the same 5-bit RGB bucket coordinates used by CreateEvictionFillerColor, then set the + // low 3 bits in each channel to 0b111. That keeps the probe inside the same coarse bucket while making + // it a different exact color, which is the shape that used to expose coarse-fallback false hits. + private static Rgba32 CreateEvictionProbeColor(int i) + { + byte r = (byte)(((i & 31) << 3) | 0x07); + byte g = (byte)((((i >> 5) & 31) << 3) | 0x07); + byte b = (byte)((((i >> 10) & 31) << 3) | 0x07); + return new(r, g, b); + } + + private static PixelMap CreatePixelMap(Rgba32[] palette) + where TCache : struct, IColorIndexCache + => new EuclideanPixelMap(Configuration.Default, palette); + + private static void AssertMatchesUncached(PixelMap exact, PixelMap cachedExact, Rgba32 color) + { + int exactIndex = exact.GetClosestColor(color, out Rgba32 exactMatch); + int cachedIndex = cachedExact.GetClosestColor(color, out Rgba32 cachedMatch); + + Assert.Equal(exactIndex, cachedIndex); + Assert.Equal(exactMatch, cachedMatch); + } + + private static Rgba32[] CreateGrayscalePalette(int count) + { + Rgba32[] palette = new Rgba32[count]; + for (int i = 0; i < count; i++) + { + byte value = count == 1 ? (byte)0 : (byte)((i * 255) / (count - 1)); + palette[i] = new Rgba32(value, value, value); + } + + return palette; + } + + // Generate a deterministic pseudo-image where each channel uses a different x/y slope. + // Neighboring pixels stay correlated, like real image content, but the combined RGB values + // churn heavily enough that exact repeats are rare. That makes this a useful stress input + // for verifying cached exact matching against an uncached baseline under dither-like access. + private static Image CreateDitherStressImage() + { + Image image = new(192, 96); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + image[x, y] = new Rgba32( + (byte)((x * 17) + (y * 13)), + (byte)((x * 29) + (y * 7)), + (byte)((x * 11) + (y * 23))); + } + } + + return image; + } + + private readonly struct UncachedCache : IColorIndexCache + { + public static UncachedCache Create(MemoryAllocator allocator) => default; + + public bool TryAdd(Rgba32 color, short value) => true; + + public bool TryGetValue(Rgba32 color, out short value) + { + value = -1; + return false; + } + + public void Clear() + { + } + + public void Dispose() + { + } + } } From f0ce591a64381458cb276231a6c30c400fafe658 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Apr 2026 22:14:58 +1000 Subject: [PATCH 117/240] Rename quantizer and update tests --- src/ImageSharp/Advanced/AotCompilerTools.cs | 8 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 6 +- src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 2 +- .../Formats/Tiff/TiffEncoderCore.cs | 2 +- .../Quantization/QuantizeExtensions.cs | 8 +- src/ImageSharp/Processing/KnownQuantizers.cs | 8 +- ...eQuantizer.cs => HexadecatreeQuantizer.cs} | 22 +- ...l}.cs => HexadecatreeQuantizer{TPixel}.cs} | 354 ++++++++++-------- .../Codecs/Png/EncodeIndexedPng.cs | 12 +- .../Formats/Bmp/BmpEncoderTests.cs | 8 +- .../Formats/GeneralFormatTests.cs | 2 +- .../Formats/Gif/GifEncoderTests.cs | 6 +- .../Formats/WebP/WebpEncoderTests.cs | 10 +- ...Tests.cs => HexadecatreeQuantizerTests.cs} | 20 +- .../Processors/Quantization/QuantizerTests.cs | 26 +- .../Quantization/QuantizedImageTests.cs | 14 +- ...tColor_WithHexadecatreeQuantizer_rgb32.bmp | 3 + ...zed_Encode_Artifacts_Rgba32_issue_2469.png | 4 +- ...Bike_HexadecatreeQuantizer_ErrorDither.png | 3 + ...ox_Bike_HexadecatreeQuantizer_NoDither.png | 3 + ...e_HexadecatreeQuantizer_OrderedDither.png} | 0 ...InBox_Bike_OctreeQuantizer_ErrorDither.png | 3 - ...ionInBox_Bike_OctreeQuantizer_NoDither.png | 3 - ...ke_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ..._Bike_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ike_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...x_Bike_WernerPaletteQuantizer_NoDither.png | 4 +- ...e_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...tionInBox_Bike_WuQuantizer_ErrorDither.png | 4 +- ...izationInBox_Bike_WuQuantizer_NoDither.png | 4 +- ...tial_HexadecatreeQuantizer_ErrorDither.png | 3 + ...Partial_HexadecatreeQuantizer_NoDither.png | 3 + ...al_HexadecatreeQuantizer_OrderedDither.png | 3 + ...oraPartial_OctreeQuantizer_ErrorDither.png | 3 - ...iphoraPartial_OctreeQuantizer_NoDither.png | 3 - ...aPartial_OctreeQuantizer_OrderedDither.png | 3 - ...al_WebSafePaletteQuantizer_ErrorDither.png | 4 +- ...rtial_WebSafePaletteQuantizer_NoDither.png | 4 +- ..._WebSafePaletteQuantizer_OrderedDither.png | 4 +- ...ial_WernerPaletteQuantizer_ErrorDither.png | 4 +- ...artial_WernerPaletteQuantizer_NoDither.png | 4 +- ...l_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...liphoraPartial_WuQuantizer_ErrorDither.png | 4 +- ...CalliphoraPartial_WuQuantizer_NoDither.png | 4 +- ...phoraPartial_WuQuantizer_OrderedDither.png | 4 +- ...HexadecatreeQuantizer_ErrorDither_0.25.png | 3 + ..._HexadecatreeQuantizer_ErrorDither_0.5.png | 3 + ...HexadecatreeQuantizer_ErrorDither_0.75.png | 3 + ...id_HexadecatreeQuantizer_ErrorDither_0.png | 3 + ...id_HexadecatreeQuantizer_ErrorDither_1.png | 3 + ...adecatreeQuantizer_OrderedDither_0.25.png} | 0 ...xadecatreeQuantizer_OrderedDither_0.5.png} | 0 ...adecatreeQuantizer_OrderedDither_0.75.png} | 0 ..._HexadecatreeQuantizer_OrderedDither_0.png | 3 + ...HexadecatreeQuantizer_OrderedDither_1.png} | 0 ...david_OctreeQuantizer_ErrorDither_0.25.png | 3 - ..._david_OctreeQuantizer_ErrorDither_0.5.png | 3 - ...david_OctreeQuantizer_ErrorDither_0.75.png | 3 - ...le_david_OctreeQuantizer_ErrorDither_0.png | 3 - ...le_david_OctreeQuantizer_ErrorDither_1.png | 3 - ..._david_OctreeQuantizer_OrderedDither_0.png | 3 - ...WernerPaletteQuantizer_OrderedDither_1.png | 4 +- ...ale_david_WuQuantizer_ErrorDither_0.25.png | 4 +- ...cale_david_WuQuantizer_ErrorDither_0.5.png | 4 +- ...ale_david_WuQuantizer_ErrorDither_0.75.png | 4 +- ...gScale_david_WuQuantizer_ErrorDither_0.png | 4 +- ...gScale_david_WuQuantizer_ErrorDither_1.png | 4 +- ...e_david_WuQuantizer_OrderedDither_0.25.png | 4 +- ...e_david_WuQuantizer_OrderedDither_0.75.png | 4 +- ...cale_david_WuQuantizer_OrderedDither_0.png | 4 +- ...cale_david_WuQuantizer_OrderedDither_1.png | 4 +- ...ike_HexadecatreeQuantizer_ErrorDither.png} | 0 ...n_Bike_HexadecatreeQuantizer_NoDither.png} | 0 ...e_HexadecatreeQuantizer_OrderedDither.png} | 0 ...e_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...ntization_Bike_WuQuantizer_ErrorDither.png | 4 +- ...Quantization_Bike_WuQuantizer_NoDither.png | 4 +- ...ization_Bike_WuQuantizer_OrderedDither.png | 4 +- ...tial_HexadecatreeQuantizer_ErrorDither.png | 3 + ...Partial_HexadecatreeQuantizer_NoDither.png | 3 + ...l_HexadecatreeQuantizer_OrderedDither.png} | 0 ...oraPartial_OctreeQuantizer_ErrorDither.png | 3 - ...iphoraPartial_OctreeQuantizer_NoDither.png | 3 - ...l_WernerPaletteQuantizer_OrderedDither.png | 4 +- ...liphoraPartial_WuQuantizer_ErrorDither.png | 4 +- ...CalliphoraPartial_WuQuantizer_NoDither.png | 4 +- ...phoraPartial_WuQuantizer_OrderedDither.png | 4 +- 90 files changed, 383 insertions(+), 354 deletions(-) rename src/ImageSharp/Processing/Processors/Quantization/{OctreeQuantizer.cs => HexadecatreeQuantizer.cs} (51%) rename src/ImageSharp/Processing/Processors/Quantization/{OctreeQuantizer{TPixel}.cs => HexadecatreeQuantizer{TPixel}.cs} (54%) rename tests/ImageSharp.Tests/Processing/Processors/Quantization/{OctreeQuantizerTests.cs => HexadecatreeQuantizerTests.cs} (76%) create mode 100644 tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png => ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png} (100%) delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png => ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png} (100%) rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png => ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png} (100%) rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png => ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png} (100%) create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png => ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png} (100%) delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png => ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png} (100%) rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantization_Bike_OctreeQuantizer_NoDither.png => ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png} (100%) rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png => ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png} (100%) create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png create mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png rename tests/Images/External/ReferenceOutput/QuantizerTests/{ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png => ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png} (100%) delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png delete mode 100644 tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 2944b58e5..0f28b2890 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -54,7 +54,7 @@ internal static class AotCompilerTools /// /// This method doesn't actually do anything but serves an important purpose... /// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception: - /// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode." + /// "Attempting to JIT compile method... HexadecatreeQuantizer.ConstructPalette... while running in aot-only mode." /// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT /// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on /// iOS so it bombs out. @@ -479,7 +479,7 @@ internal static class AotCompilerTools private static void AotCompileQuantizers() where TPixel : unmanaged, IPixel { - AotCompileQuantizer(); + AotCompileQuantizer(); AotCompileQuantizer(); AotCompileQuantizer(); AotCompileQuantizer(); @@ -549,8 +549,8 @@ internal static class AotCompilerTools where TPixel : unmanaged, IPixel where TDither : struct, IDither { - OctreeQuantizer octree = default; - default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default); + HexadecatreeQuantizer hexadecatree = default; + default(TDither).ApplyQuantizationDither, TPixel>(ref hexadecatree, default, default, default); PaletteQuantizer palette = default; default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index e25556804..210c08464 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -13,7 +13,7 @@ public sealed class BmpEncoder : QuantizingImageEncoder /// /// Initializes a new instance of the class. /// - public BmpEncoder() => this.Quantizer = KnownQuantizers.Octree; + public BmpEncoder() => this.Quantizer = KnownQuantizers.Hexadecatree; /// /// Gets the number of bits per pixel. diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index ccc620d6c..0bf57c561 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -116,7 +116,7 @@ internal sealed class BmpEncoderCore this.bitsPerPixel = encoder.BitsPerPixel; // TODO: Use a palette quantizer if supplied. - this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; + this.quantizer = encoder.Quantizer ?? KnownQuantizers.Hexadecatree; this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; this.transparentColorMode = encoder.TransparentColorMode; this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 07c73dcf2..d2883e281 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -117,7 +117,7 @@ internal sealed class GifEncoderCore if (globalQuantizer is null) { - // Is this a gif with color information. If so use that, otherwise use octree. + // Is this a gif with color information. If so use that, otherwise use the adaptive hexadecatree quantizer. if (gifMetadata.ColorTableMode == FrameColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0) { int ti = GetTransparentIndex(quantized, frameMetadata); @@ -132,12 +132,12 @@ internal sealed class GifEncoderCore } else { - globalQuantizer = new OctreeQuantizer(options); + globalQuantizer = new HexadecatreeQuantizer(options); } } else { - globalQuantizer = new OctreeQuantizer(options); + globalQuantizer = new HexadecatreeQuantizer(options); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index a068613bf..7859b2c90 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -15,7 +15,7 @@ public class TiffEncoder : QuantizingImageEncoder /// /// Initializes a new instance of the class. /// - public TiffEncoder() => this.Quantizer = KnownQuantizers.Octree; + public TiffEncoder() => this.Quantizer = KnownQuantizers.Hexadecatree; /// /// Gets the number of bits per pixel. diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index d7508b02e..e5e47166e 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -71,7 +71,7 @@ internal sealed class TiffEncoderCore this.configuration = configuration; this.memoryAllocator = configuration.MemoryAllocator; this.PhotometricInterpretation = encoder.PhotometricInterpretation; - this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; + this.quantizer = encoder.Quantizer ?? KnownQuantizers.Hexadecatree; this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; this.BitsPerPixel = encoder.BitsPerPixel; this.HorizontalPredictor = encoder.HorizontalPredictor; diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs index bf6d2221f..b0f5cb7d6 100644 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -12,12 +12,12 @@ namespace SixLabors.ImageSharp.Processing; public static class QuantizeExtensions { /// - /// Applies quantization to the image using the . + /// Applies quantization to the image using the . /// /// The current image processing context. /// The . public static IImageProcessingContext Quantize(this IImageProcessingContext source) => - Quantize(source, KnownQuantizers.Octree); + Quantize(source, KnownQuantizers.Hexadecatree); /// /// Applies quantization to the image. @@ -29,7 +29,7 @@ public static class QuantizeExtensions source.ApplyProcessor(new QuantizeProcessor(quantizer)); /// - /// Applies quantization to the image using the . + /// Applies quantization to the image using the . /// /// The current image processing context. /// @@ -37,7 +37,7 @@ public static class QuantizeExtensions /// /// The . public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => - Quantize(source, KnownQuantizers.Octree, rectangle); + Quantize(source, KnownQuantizers.Hexadecatree, rectangle); /// /// Applies quantization to the image. diff --git a/src/ImageSharp/Processing/KnownQuantizers.cs b/src/ImageSharp/Processing/KnownQuantizers.cs index 6fb3c72e8..b63ba597d 100644 --- a/src/ImageSharp/Processing/KnownQuantizers.cs +++ b/src/ImageSharp/Processing/KnownQuantizers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -6,14 +6,14 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing; /// -/// Contains reusable static instances of known quantizing algorithms +/// Contains reusable static instances of known quantizing algorithms. /// public static class KnownQuantizers { /// - /// Gets the adaptive Octree quantizer. Fast with good quality. + /// Gets the adaptive hexadecatree quantizer. Fast with good quality. /// - public static IQuantizer Octree { get; } = new OctreeQuantizer(); + public static IQuantizer Hexadecatree { get; } = new HexadecatreeQuantizer(); /// /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs similarity index 51% rename from src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs rename to src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs index 0a1032bf0..6b2f5a013 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs @@ -6,25 +6,29 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// -/// Allows the quantization of images pixels using Octrees. -/// +/// Quantizes images by grouping colors in an adaptive 16-way tree and reducing those groups into a palette. /// -public class OctreeQuantizer : IQuantizer +/// +/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha. Fully opaque mid-tone colors +/// use RGB-only routing so more branch resolution is spent on visible color detail, while transparent, dark, and +/// light colors use alpha-aware routing so opacity changes can form their own palette buckets. +/// +public class HexadecatreeQuantizer : IQuantizer { /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// using the default . /// - public OctreeQuantizer() + public HexadecatreeQuantizer() : this(new QuantizerOptions()) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The quantizer options defining quantization rules. - public OctreeQuantizer(QuantizerOptions options) + /// The quantizer options that control palette size, dithering, and transparency behavior. + public HexadecatreeQuantizer(QuantizerOptions options) { Guard.NotNull(options, nameof(options)); this.Options = options; @@ -41,5 +45,5 @@ public class OctreeQuantizer : IQuantizer /// public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : unmanaged, IPixel - => new OctreeQuantizer(configuration, options); + => new HexadecatreeQuantizer(configuration, options); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs similarity index 54% rename from src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs index bdf2ba20a..b5d39d73e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs @@ -12,19 +12,28 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// -/// Encapsulates methods to calculate the color palette if an image using an Octree pattern. -/// +/// Quantizes an image by building an adaptive 16-way color tree and reducing it to the requested palette size. /// +/// +/// +/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha, giving the tree up to 16 children +/// per node and letting transparency participate directly in palette construction. +/// +/// +/// Fully opaque mid-tone colors use RGB-only routing so more branch resolution is spent on visible color detail. +/// Transparent, dark, and light colors use alpha-aware routing so opacity changes can form distinct palette buckets. +/// +/// /// The pixel format. #pragma warning disable CA1001 // Types that own disposable fields should be disposable // See https://github.com/dotnet/roslyn-analyzers/issues/6151 -public struct OctreeQuantizer : IQuantizer +public struct HexadecatreeQuantizer : IQuantizer #pragma warning restore CA1001 // Types that own disposable fields should be disposable where TPixel : unmanaged, IPixel { private readonly int maxColors; private readonly int bitDepth; - private readonly Octree octree; + private readonly Hexadecatree tree; private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private PixelMap? pixelMap; @@ -32,19 +41,19 @@ public struct OctreeQuantizer : IQuantizer private bool isDisposed; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. + /// The configuration that provides memory allocation and pixel conversion services. + /// The quantizer options that control palette size, dithering, and transparency behavior. [MethodImpl(InliningOptions.ShortMethod)] - public OctreeQuantizer(Configuration configuration, QuantizerOptions options) + public HexadecatreeQuantizer(Configuration configuration, QuantizerOptions options) { this.Configuration = configuration; this.Options = options; this.maxColors = this.Options.MaxColors; this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold); + this.tree = new Hexadecatree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; this.palette = default; @@ -76,23 +85,28 @@ public struct OctreeQuantizer : IQuantizer /// public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) { - PixelRowDelegate pixelRowDelegate = new(this.octree); - QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( + PixelRowDelegate pixelRowDelegate = new(this.tree); + QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( ref Unsafe.AsRef(in this), in pixelRegion, in pixelRowDelegate); } + /// + /// Materializes the final palette from the accumulated tree and prepares the dither lookup map when needed. + /// private void ResolvePalette() { short paletteIndex = 0; Span paletteSpan = this.paletteOwner.GetSpan(); - this.octree.Palettize(paletteSpan, ref paletteIndex); + this.tree.Palettize(paletteSpan, ref paletteIndex); ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; if (this.isDithering) { + // Dithered colors often no longer land on a color that was seen during palette construction, + // so the quantization pass switches to nearest-palette matching once the palette is finalized. this.pixelMap = PixelMapFactory.Create(this.Configuration, result, this.Options.ColorMatchingMode); } @@ -108,17 +122,15 @@ public struct OctreeQuantizer : IQuantizer [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - // Due to the addition of new colors by dithering that are not part of the original histogram, - // the octree nodes might not match the correct color. - // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { + // Dithering introduces adjusted colors that were never inserted into the tree, so tree lookup + // is only reliable for the non-dithered path. return (byte)this.pixelMap!.GetClosestColor(color, out match); } ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - - int index = this.octree.GetPaletteIndex(color); + int index = this.tree.GetPaletteIndex(color); match = Unsafe.Add(ref paletteRef, (nuint)index); return (byte)index; } @@ -132,34 +144,43 @@ public struct OctreeQuantizer : IQuantizer this.paletteOwner.Dispose(); this.pixelMap?.Dispose(); this.pixelMap = null; - this.octree.Dispose(); + this.tree.Dispose(); } } + /// + /// Forwards source rows into the tree without creating an intermediate buffer. + /// private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate { - private readonly Octree octree; + private readonly Hexadecatree tree; - public PixelRowDelegate(Octree octree) => this.octree = octree; + /// + /// Initializes a new instance of the struct. + /// + /// The destination tree that should accumulate each visited row. + public PixelRowDelegate(Hexadecatree tree) => this.tree = tree; - public void Invoke(ReadOnlySpan row, int rowIndex) => this.octree.AddColors(row); + /// + public void Invoke(ReadOnlySpan row, int rowIndex) => this.tree.AddColors(row); } /// - /// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation. - /// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores - /// color accumulation data, and supports dynamic node allocation and reduction. It offers near-constant-time insertions - /// and lookups while consuming roughly 240 KB for the node pool. + /// Stores the adaptive 16-way partition tree used to accumulate colors and emit palette entries. /// - internal sealed class Octree : IDisposable + /// + /// The tree uses a fixed node arena for predictable allocation behavior, keeps per-level reducible node lists so + /// deeper buckets can be merged until the palette fits, and caches the previously inserted leaf so repeated colors + /// can be accumulated cheaply. + /// + internal sealed class Hexadecatree : IDisposable { - // The memory allocator. - private readonly MemoryAllocator allocator; - // Pooled buffer for OctreeNodes. - private readonly IMemoryOwner nodesOwner; + private readonly IMemoryOwner nodesOwner; - // Reducible nodes: one per level; we use an integer index; -1 means “no node.” + // One reducible-node head per level. + // Each entry stores a node index, or -1 when that level currently + // has no reducible nodes. private readonly short[] reducibleNodes; // Maximum number of allowable colors. @@ -186,13 +207,13 @@ public struct OctreeQuantizer : IQuantizer private readonly Stack freeIndices = new(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The configuration which allows altering default behavior or extending the library. - /// The maximum number of significant bits in the image. - /// The maximum number of colors to allow in the palette. - /// The threshold for transparent colors. - public Octree( + /// The configuration that provides the backing memory allocator. + /// The number of levels to descend before forcing leaves. + /// The maximum number of palette entries the reduced tree may retain. + /// The alpha threshold below which generated palette entries become fully transparent. + public Hexadecatree( Configuration configuration, int maxColorBits, int maxColors, @@ -207,8 +228,7 @@ public struct OctreeQuantizer : IQuantizer // Allocate a conservative buffer for nodes. const int capacity = 4096; - this.allocator = configuration.MemoryAllocator; - this.nodesOwner = this.allocator.Allocate(capacity, AllocationOptions.Clean); + this.nodesOwner = configuration.MemoryAllocator.Allocate(capacity, AllocationOptions.Clean); // Create the reducible nodes array (one per level 0 .. maxColorBits-1). this.reducibleNodes = new short[this.maxColorBits]; @@ -216,24 +236,24 @@ public struct OctreeQuantizer : IQuantizer // Reserve index 0 for the root. this.rootIndex = 0; - ref OctreeNode root = ref this.Nodes[this.rootIndex]; + ref Node root = ref this.Nodes[this.rootIndex]; root.Initialize(0, this.maxColorBits, this, this.rootIndex); } /// - /// Gets or sets the number of leaves in the tree. + /// Gets or sets the number of leaf nodes currently representing palette buckets. /// public int Leaves { get; set; } /// - /// Gets the full collection of nodes as a span. + /// Gets the underlying node arena. /// - internal Span Nodes => this.nodesOwner.Memory.Span; + internal Span Nodes => this.nodesOwner.Memory.Span; /// - /// Adds a span of colors to the octree. + /// Adds a row of colors to the tree. /// - /// A span of color values to be added. + /// The colors to accumulate. public void AddColors(ReadOnlySpan row) { for (int x = 0; x < row.Length; x++) @@ -243,12 +263,13 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Add a color to the Octree. + /// Adds a single color sample to the tree. /// - /// The color to add. + /// The color to accumulate. private void AddColor(Rgba32 color) { - // Ensure that the tree is not already full. + // Once the node arena is full and there are no recycled slots available, keep collapsing + // reducible leaves until the tree is small enough to make forward progress again. if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0) { while (this.Leaves > this.maxColors) @@ -257,32 +278,32 @@ public struct OctreeQuantizer : IQuantizer } } - // If the color is the same as the previous color, increment the node. - // Otherwise, add a new node. + // Scanlines often contain long runs of the same color. Caching the previous leaf lets those + // repeats skip the tree walk and just bump the accumulated sums in place. if (this.previousColor.Equals(color)) { if (this.previousNode == -1) { this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); } else { - OctreeNode.Increment(this.previousNode, color, this); + Node.Increment(this.previousNode, color, this); } } else { this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); } } /// - /// Construct the palette from the octree. + /// Reduces the tree to the requested palette size and emits the final palette entries. /// - /// The palette to construct. - /// The current palette index. + /// The destination palette span. + /// The running palette index. public void Palettize(Span palette, ref short paletteIndex) { while (this.Leaves > this.maxColors) @@ -294,48 +315,45 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Get the palette index for the passed color. + /// Gets the palette index selected by the tree for the supplied color. /// - /// The color to get the palette index for. - /// The . + /// The color to resolve. + /// The palette index represented by the best matching leaf in the reduced tree. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetPaletteIndex(TPixel color) => this.Nodes[this.rootIndex].GetPaletteIndex(color.ToRgba32(), 0, this); /// - /// Track the previous node and color. + /// Records the most recently touched leaf so repeated colors can bypass another descent. /// - /// The node index. + /// The leaf node index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrackPrevious(int nodeIndex) => this.previousNode = nodeIndex; /// - /// Reduce the depth of the tree. + /// Collapses the deepest currently reducible node into a single leaf. /// private void Reduce() { - // Find the deepest level containing at least one reducible node int index = this.maxColorBits - 1; while ((index > 0) && (this.reducibleNodes[index] == -1)) { index--; } - // Reduce the node most recently added to the list at level 'index' - ref OctreeNode node = ref this.Nodes[this.reducibleNodes[index]]; + ref Node node = ref this.Nodes[this.reducibleNodes[index]]; this.reducibleNodes[index] = node.NextReducibleIndex; - - // Decrement the leaf count after reducing the node node.Reduce(this); - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... + // If the last inserted leaf was merged away, the next repeated color must walk the tree again. this.previousNode = -1; } - // Allocate a new OctreeNode from the pooled buffer. - // First check the freeIndices stack. + /// + /// Allocates a node index from the free list or from the unused tail of the arena. + /// + /// The allocated node index, or -1 if no node can be allocated. internal short AllocateNode() { if (this.freeIndices.Count > 0) @@ -354,9 +372,9 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Free a node index, making it available for re-allocation. + /// Returns a node index to the free list. /// - /// The index to free. + /// The node index to recycle. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void FreeNode(short index) { @@ -367,8 +385,11 @@ public struct OctreeQuantizer : IQuantizer /// public void Dispose() => this.nodesOwner.Dispose(); + /// + /// Represents one node in the hexadecatree node arena. + /// [StructLayout(LayoutKind.Sequential)] - internal struct OctreeNode + internal struct Node { public bool Leaf; public int PixelCount; @@ -380,19 +401,21 @@ public struct OctreeQuantizer : IQuantizer public short NextReducibleIndex; private InlineArray16 children; + /// + /// Gets the 16 child slots for this node. + /// [UnscopedRef] public Span Children => this.children; /// - /// Initialize the . + /// Initializes a node either as a leaf or as a reducible interior node. /// - /// The level of the node. - /// The number of significant color bits in the image. - /// The parent octree. - /// The index of the node. - public void Initialize(int level, int colorBits, Octree octree, short index) + /// The depth of the node being initialized. + /// The maximum tree depth. + /// The owning tree. + /// The node index in the arena. + public void Initialize(int level, int colorBits, Hexadecatree tree, short index) { - // Construct the new node. this.Leaf = level == colorBits; this.Red = 0; this.Green = 0; @@ -401,76 +424,73 @@ public struct OctreeQuantizer : IQuantizer this.PixelCount = 0; this.PaletteIndex = 0; this.NextReducibleIndex = -1; - - // Always clear the Children array. this.Children.Fill(-1); if (this.Leaf) { - octree.Leaves++; + tree.Leaves++; } else { - // Add this node to the reducible nodes list for its level. - this.NextReducibleIndex = octree.reducibleNodes[level]; - octree.reducibleNodes[level] = index; + // Track reducible nodes per level so palette reduction can always collapse the deepest + // buckets first without scanning the entire arena. + this.NextReducibleIndex = tree.reducibleNodes[level]; + tree.reducibleNodes[level] = index; } } /// - /// Add a color to the Octree. + /// Descends the tree for the supplied color, allocating nodes as needed until a leaf is reached. /// - /// The node index. - /// The color to add. - /// The number of significant color bits in the image. - /// The level of the node. - /// The parent octree. - public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Octree octree) + /// The current node index. + /// The color being accumulated. + /// The maximum tree depth. + /// The current depth. + /// The owning tree. + public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Hexadecatree tree) { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; + ref Node node = ref tree.Nodes[nodeIndex]; if (node.Leaf) { - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); + Increment(nodeIndex, color, tree); + tree.TrackPrevious(nodeIndex); + return; } - else - { - int index = GetColorIndex(color, level); - short childIndex; - Span children = node.Children; - childIndex = children[index]; + int index = GetColorIndex(color, level); + Span children = node.Children; + short childIndex = children[index]; + if (childIndex == -1) + { + childIndex = tree.AllocateNode(); if (childIndex == -1) { - childIndex = octree.AllocateNode(); - - if (childIndex == -1) - { - // No room in the tree, so increment the count and return. - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); - return; - } - - ref OctreeNode child = ref octree.Nodes[childIndex]; - child.Initialize(level + 1, colorBits, octree, childIndex); - children[index] = childIndex; + // If the arena is exhausted and no node can be reclaimed yet, fall back to + // accumulating into the current node instead of failing the insert outright. + Increment(nodeIndex, color, tree); + tree.TrackPrevious(nodeIndex); + return; } - AddColor(childIndex, color, colorBits, level + 1, octree); + ref Node child = ref tree.Nodes[childIndex]; + child.Initialize(level + 1, colorBits, tree, childIndex); + children[index] = childIndex; } + + // Keep descending until we reach the leaf bucket that should accumulate this sample. + AddColor(childIndex, color, colorBits, level + 1, tree); } /// - /// Increment the color components of this node. + /// Adds the supplied color sample to an existing node's running sums. /// - /// The node index. - /// The color to increment by. - /// The parent octree. - public static void Increment(int nodeIndex, Rgba32 color, Octree octree) + /// The node index to update. + /// The color sample being accumulated. + /// The owning tree. + public static void Increment(int nodeIndex, Rgba32 color, Hexadecatree tree) { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; + ref Node node = ref tree.Nodes[nodeIndex]; node.PixelCount++; node.Red += color.R; node.Green += color.G; @@ -479,10 +499,10 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Reduce this node by ensuring its children are all reduced (i.e. leaves) and then merging their data. + /// Merges all child nodes into this node and turns it into a leaf. /// - /// The parent octree. - public void Reduce(Octree octree) + /// The owning tree. + public void Reduce(Hexadecatree tree) { // If already a leaf, do nothing. if (this.Leaf) @@ -492,25 +512,27 @@ public struct OctreeQuantizer : IQuantizer // Now merge the (presumably reduced) children. int pixelCount = 0; - int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0; + int sumRed = 0; + int sumGreen = 0; + int sumBlue = 0; + int sumAlpha = 0; Span children = this.Children; + for (int i = 0; i < children.Length; i++) { short childIndex = children[i]; if (childIndex != -1) { - ref OctreeNode child = ref octree.Nodes[childIndex]; + ref Node child = ref tree.Nodes[childIndex]; int pixels = child.PixelCount; - sumRed += child.Red; sumGreen += child.Green; sumBlue += child.Blue; sumAlpha += child.Alpha; pixelCount += pixels; - // Free the child immediately. children[i] = -1; - octree.FreeNode(childIndex); + tree.FreeNode(childIndex); } } @@ -529,16 +551,16 @@ public struct OctreeQuantizer : IQuantizer } this.Leaf = true; - octree.Leaves++; + tree.Leaves++; } /// - /// Traverse the tree to construct the palette. + /// Traverses the reduced tree and emits one palette color per leaf. /// - /// The parent octree. - /// The palette to construct. - /// The current palette index. - public void ConstructPalette(Octree octree, Span palette, ref short paletteIndex) + /// The owning tree. + /// The destination palette span. + /// The running palette index. + public void ConstructPalette(Hexadecatree tree, Span palette, ref short paletteIndex) { if (this.Leaf) { @@ -549,13 +571,12 @@ public struct OctreeQuantizer : IQuantizer Vector4.Zero, new Vector4(255)); - if (vector.W < octree.transparencyThreshold255) + if (vector.W < tree.transparencyThreshold255) { vector = Vector4.Zero; } palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); - this.PaletteIndex = paletteIndex++; } else @@ -566,19 +587,20 @@ public struct OctreeQuantizer : IQuantizer int childIndex = children[i]; if (childIndex != -1) { - octree.Nodes[childIndex].ConstructPalette(octree, palette, ref paletteIndex); + tree.Nodes[childIndex].ConstructPalette(tree, palette, ref paletteIndex); } } } } /// - /// Get the palette index for the passed color. + /// Resolves the palette index represented by this node for the supplied color. /// - /// The color to get the palette index for. - /// The level of the node. - /// The parent octree. - public int GetPaletteIndex(Rgba32 color, int level, Octree octree) + /// The color to resolve. + /// The current tree depth. + /// The owning tree. + /// The palette index for the best reachable leaf, or -1 if no leaf can be reached. + public int GetPaletteIndex(Rgba32 color, int level, Hexadecatree tree) { if (this.Leaf) { @@ -590,15 +612,16 @@ public struct OctreeQuantizer : IQuantizer int childIndex = children[colorIndex]; if (childIndex != -1) { - return octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + return tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree); } + // After reductions the exact branch can disappear, so fall back to the first reachable descendant leaf. for (int i = 0; i < children.Length; i++) { childIndex = children[i]; if (childIndex != -1) { - int childPaletteIndex = octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + int childPaletteIndex = tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree); if (childPaletteIndex != -1) { return childPaletteIndex; @@ -610,37 +633,35 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Gets the color index at the given level. + /// Computes the child slot for a color at the supplied tree level. /// - /// The color to get the index for. - /// The level to get the index at. + /// The color being routed. + /// The tree depth whose bit plane should be sampled. + /// The child slot index for the color at the supplied level. + /// + /// For fully opaque mid-tone colors the tree ignores alpha and routes on RGB only, preserving more branch + /// resolution for visible color detail. For transparent, dark, and light colors it includes alpha as the + /// most significant routing bit so opacity changes can form their own branches. + /// public static int GetColorIndex(Rgba32 color, int level) { - // Determine how many bits to shift based on the current tree level. - // At level 0, shift = 7; as level increases, the shift decreases. + // Sample one bit plane per level, starting at the most significant bit and moving downward. int shift = 7 - level; byte mask = (byte)(1 << shift); - // Compute the luminance of the RGB components using the BT.709 standard. - // This gives a measure of brightness for the color. + // Use BT.709 luminance as a cheap brightness estimate for deciding whether alpha carries + // useful information at this level for fully opaque colors. int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); - // Define thresholds for determining when to include the alpha bit in the index. - // The thresholds are scaled according to the current level. - // 128 is the midpoint of the 8-bit range (0–255), so shifting it right by 'level' - // produces a threshold that scales with the color cube subdivision. + // Scale the brightness thresholds with depth so deeper levels become stricter about when + // to spend a branch bit on alpha instead of RGB detail. int darkThreshold = 128 >> level; - - // The light threshold is set symmetrically: 255 minus the scaled midpoint. int lightThreshold = 255 - (128 >> level); - // If the pixel is fully opaque and its brightness falls between the dark and light thresholds, - // ignore the alpha channel to maximize RGB resolution. - // Otherwise (if the pixel is dark, light, or semi-transparent), include the alpha bit - // to preserve any gradient that may be present. if (color.A == 255 && luminance > darkThreshold && luminance < lightThreshold) { - // Extract one bit each from R, G, and B channels and combine them into a 3-bit index. + // Fully opaque mid-tone colors route on RGB only, which preserves more visible color + // resolution because alpha would contribute no extra separation here. int rBits = ((color.R & mask) >> shift) << 2; int gBits = ((color.G & mask) >> shift) << 1; int bBits = (color.B & mask) >> shift; @@ -648,7 +669,8 @@ public struct OctreeQuantizer : IQuantizer } else { - // Extract one bit from each channel including alpha (alpha becomes the most significant bit). + // Transparent, dark, and light colors include alpha as the high routing bit so opacity + // changes can form distinct buckets alongside RGB differences. int aBits = ((color.A & mask) >> shift) << 3; int rBits = ((color.R & mask) >> shift) << 2; int gBits = ((color.G & mask) >> shift) << 1; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs index 125b42680..69779731b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs @@ -40,19 +40,19 @@ public class EncodeIndexedPng this.bmpCore.Dispose(); } - [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] - public void PngCoreOctree() + [Benchmark(Baseline = true, Description = "ImageSharp Hexadecatree Png")] + public void PngCoreHexadecatree() { using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = KnownQuantizers.Octree }; + PngEncoder options = new() { Quantizer = KnownQuantizers.Hexadecatree }; this.bmpCore.SaveAsPng(memoryStream, options); } - [Benchmark(Description = "ImageSharp Octree NoDither Png")] - public void PngCoreOctreeNoDither() + [Benchmark(Description = "ImageSharp Hexadecatree NoDither Png")] + public void PngCoreHexadecatreeNoDither() { using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; + PngEncoder options = new() { Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 5ebcc8bb9..6bd7e0103 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -292,7 +292,7 @@ public class BmpEncoderTests [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + public void Encode_8BitColor_WithHexadecatreeQuantizer(TestImageProvider provider) where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) @@ -304,7 +304,7 @@ public class BmpEncoderTests BmpEncoder encoder = new() { BitsPerPixel = BmpBitsPerPixel.Bit8, - Quantizer = new OctreeQuantizer() + Quantizer = new HexadecatreeQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); @@ -385,7 +385,7 @@ public class BmpEncoderTests { BitsPerPixel = bitsPerPixel, SupportTransparency = false, - Quantizer = KnownQuantizers.Octree + Quantizer = KnownQuantizers.Hexadecatree }; image.SaveAsBmp(reencodedStream, encoder); reencodedStream.Seek(0, SeekOrigin.Begin); @@ -478,7 +478,7 @@ public class BmpEncoderTests { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree + Quantizer = quantizer ?? KnownQuantizers.Hexadecatree }; // Does DebugSave & load reference CompareToReferenceInput(): diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 072b04fa0..2b91c4dbf 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -125,7 +125,7 @@ public class GeneralFormatTests public static readonly TheoryData QuantizerNames = new() { - nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.Hexadecatree), nameof(KnownQuantizers.WebSafe), nameof(KnownQuantizers.Werner), nameof(KnownQuantizers.Wu) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 370106ca3..b7bbe4971 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -115,7 +115,7 @@ public class GifEncoderTests GifEncoder encoder = new() { ColorTableMode = FrameColorTableMode.Global, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -124,7 +124,7 @@ public class GifEncoderTests encoder = new GifEncoder { ColorTableMode = FrameColorTableMode.Local, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }), + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }), }; provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); @@ -191,7 +191,7 @@ public class GifEncoderTests GifEncoder encoder = new() { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = maxColors }) + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { MaxColors = maxColors }) }; image.Save(outStream, encoder); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index f9836ffb1..ee8268716 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -135,9 +135,9 @@ public class WebpEncoderTests // Alpha thresholding is 64/255F. GifEncoder gifEncoder = new() { - Quantizer = new OctreeQuantizer(options) + Quantizer = new HexadecatreeQuantizer(options) }; - provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "octree"); + provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "hexadecatree"); gifEncoder = new GifEncoder { @@ -152,8 +152,8 @@ public class WebpEncoderTests }; using Image cloned1 = image.Clone(); - cloned1.Mutate(c => c.Quantize(new OctreeQuantizer(options))); - provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "octree"); + cloned1.Mutate(c => c.Quantize(new HexadecatreeQuantizer(options))); + provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "hexadecatree"); using Image cloned2 = image.Clone(); cloned2.Mutate(c => c.Quantize(new WuQuantizer(options))); @@ -162,7 +162,7 @@ public class WebpEncoderTests // Now blend the images with a blue background and save as webp. using Image background1 = new(image.Width, image.Height, Color.White.ToPixel()); background1.Mutate(c => c.DrawImage(cloned1, 1)); - provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "octree-blended"); + provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "hexadecatree-blended"); using Image background2 = new(image.Width, image.Height, Color.White.ToPixel()); background2.Mutate(c => c.DrawImage(cloned2, 1)); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs similarity index 76% rename from tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs index c9f3daf0f..4ef215930 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs @@ -8,37 +8,37 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; [Trait("Category", "Processors")] -public class OctreeQuantizerTests +public class HexadecatreeQuantizerTests { [Fact] - public void OctreeQuantizerConstructor() + public void HexadecatreeQuantizerConstructor() { QuantizerOptions expected = new() { MaxColors = 128 }; - OctreeQuantizer quantizer = new(expected); + HexadecatreeQuantizer quantizer = new(expected); Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); expected = new QuantizerOptions { Dither = null }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); Assert.Null(quantizer.Options.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] - public void OctreeQuantizerCanCreateFrameQuantizer() + public void HexadecatreeQuantizerCanCreateFrameQuantizer() { - OctreeQuantizer quantizer = new(); + HexadecatreeQuantizer quantizer = new(); IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); @@ -46,14 +46,14 @@ public class OctreeQuantizerTests Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); + quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.Null(frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index 2ba757c11..00e09d83b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -74,15 +74,15 @@ public class QuantizerTests = new() { // Known uses error diffusion by default. - KnownQuantizers.Octree, + KnownQuantizers.Hexadecatree, KnownQuantizers.WebSafe, KnownQuantizers.Werner, KnownQuantizers.Wu, - new OctreeQuantizer(NoDitherOptions), + new HexadecatreeQuantizer(NoDitherOptions), new WebSafePaletteQuantizer(NoDitherOptions), new WernerPaletteQuantizer(NoDitherOptions), new WuQuantizer(NoDitherOptions), - new OctreeQuantizer(OrderedDitherOptions), + new HexadecatreeQuantizer(OrderedDitherOptions), new WebSafePaletteQuantizer(OrderedDitherOptions), new WernerPaletteQuantizer(OrderedDitherOptions), new WuQuantizer(OrderedDitherOptions) @@ -91,52 +91,52 @@ public class QuantizerTests public static readonly TheoryData DitherScaleQuantizers = new() { - new OctreeQuantizer(Diffuser0_ScaleDitherOptions), + new HexadecatreeQuantizer(Diffuser0_ScaleDitherOptions), new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), new WuQuantizer(Diffuser0_ScaleDitherOptions), - new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), + new HexadecatreeQuantizer(Diffuser0_25_ScaleDitherOptions), new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), new WuQuantizer(Diffuser0_25_ScaleDitherOptions), - new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), + new HexadecatreeQuantizer(Diffuser0_5_ScaleDitherOptions), new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), new WuQuantizer(Diffuser0_5_ScaleDitherOptions), - new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), + new HexadecatreeQuantizer(Diffuser0_75_ScaleDitherOptions), new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), new WuQuantizer(Diffuser0_75_ScaleDitherOptions), - new OctreeQuantizer(DiffuserDitherOptions), + new HexadecatreeQuantizer(DiffuserDitherOptions), new WebSafePaletteQuantizer(DiffuserDitherOptions), new WernerPaletteQuantizer(DiffuserDitherOptions), new WuQuantizer(DiffuserDitherOptions), - new OctreeQuantizer(Ordered0_ScaleDitherOptions), + new HexadecatreeQuantizer(Ordered0_ScaleDitherOptions), new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), new WuQuantizer(Ordered0_ScaleDitherOptions), - new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), + new HexadecatreeQuantizer(Ordered0_25_ScaleDitherOptions), new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), new WuQuantizer(Ordered0_25_ScaleDitherOptions), - new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), + new HexadecatreeQuantizer(Ordered0_5_ScaleDitherOptions), new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), new WuQuantizer(Ordered0_5_ScaleDitherOptions), - new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), + new HexadecatreeQuantizer(Ordered0_75_ScaleDitherOptions), new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), new WuQuantizer(Ordered0_75_ScaleDitherOptions), - new OctreeQuantizer(OrderedDitherOptions), + new HexadecatreeQuantizer(OrderedDitherOptions), new WebSafePaletteQuantizer(OrderedDitherOptions), new WernerPaletteQuantizer(OrderedDitherOptions), new WuQuantizer(OrderedDitherOptions), diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index d832136a9..e94088652 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -15,12 +15,12 @@ public class QuantizedImageTests { WernerPaletteQuantizer werner = new(); WebSafePaletteQuantizer webSafe = new(); - OctreeQuantizer octree = new(); + HexadecatreeQuantizer hexadecatree = new(); WuQuantizer wu = new(); Assert.NotNull(werner.Options.Dither); Assert.NotNull(webSafe.Options.Dither); - Assert.NotNull(octree.Options.Dither); + Assert.NotNull(hexadecatree.Options.Dither); Assert.NotNull(wu.Options.Dither); using (IQuantizer quantizer = werner.CreatePixelSpecificQuantizer(this.Configuration)) @@ -33,7 +33,7 @@ public class QuantizedImageTests Assert.NotNull(quantizer.Options.Dither); } - using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) + using (IQuantizer quantizer = hexadecatree.CreatePixelSpecificQuantizer(this.Configuration)) { Assert.NotNull(quantizer.Options.Dither); } @@ -47,7 +47,7 @@ public class QuantizedImageTests [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void OctreeQuantizerYieldsCorrectTransparentPixel( + public void HexadecatreeQuantizerYieldsCorrectTransparentPixel( TestImageProvider provider, bool dither) where TPixel : unmanaged, IPixel @@ -60,7 +60,7 @@ public class QuantizedImageTests options.Dither = null; } - OctreeQuantizer quantizer = new(options); + HexadecatreeQuantizer quantizer = new(options); foreach (ImageFrame frame in image.Frames) { @@ -103,8 +103,8 @@ public class QuantizedImageTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - OctreeQuantizer octreeQuantizer = new(); - IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions { MaxColors = 128 }); + HexadecatreeQuantizer hexadecatreeQuantizer = new(); + IQuantizer quantizer = hexadecatreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions { MaxColors = 128 }); ImageFrame frame = image.Frames[0]; quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); } diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp new file mode 100644 index 000000000..f4ae3b9b6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 +size 9270 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png index 4c7830375..ecf0691cd 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3 -size 945821 +oid sha256:770061fbb29cd20bc700ce3fc57e38a758c632c3e89de51f5fbee3d5d522539e +size 912635 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..d2b62e63a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27f6e8e195c4431dc7354a379152d3a8664582bc2bb1c8960ebf4088aa6505e2 +size 248709 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..ecbf328d3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46b5751dc43e9ad5541913cf851ef1b061aa474a95283c712511531202d7015e +size 239326 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 327366f5b..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0086044f12a7c58e49733f203af29a8aff2826ea654730274720eada15669254 -size 249163 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png deleted file mode 100644 index 3e0be536e..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85ee8479984aa52f837badbc49085c5448597fbfd987438fe25b58bad475e85f -size 239498 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png index 922c2bf9b..28db1b73a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f +size 215873 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png index 922c2bf9b..28db1b73a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f +size 215873 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png index 29c93d14e..078c75c45 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a -size 226727 +oid sha256:5eb87f02c7924b764bbd2c951047b7204c56a0a1a0d6853a0fb3d30a56ed0184 +size 226633 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png index dbfab2b50..e80b9b8b1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3 +size 220099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png index dbfab2b50..e80b9b8b1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3 +size 220099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png index 86655af42..ad899553d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f -size 230883 +oid sha256:c4548abed72e4f833b33eed14392206d7232112fc651becb2351fdee27da5bc1 +size 230687 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png index 82d5e5d59..a30d69d17 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d -size 263152 +oid sha256:832173c8ca6bd7a8bf417d83b459ccddb541daed1c31539bf596cacea455441d +size 263018 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png index d8a1178ad..e5591852b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095 -size 262298 +oid sha256:15a6dc485f0c3fd4c9fbbdb6b50437d58d68210790e37f8aab32e66a864e2746 +size 261872 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..2e815d4d1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6eeed563b407940e2a05f068c42b52738e6e1217a1500c9230f7068ca4e9f1e +size 304162 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..a8f30e5f5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dc7dc55af4ef0741a66c569876ad8a2df27164a653baa5bae536e6d121b2c11 +size 300528 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png new file mode 100644 index 000000000..3ece7ee28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b65e7903fbfa1ed0682221fdd86c6f0448b3f6a886cae5379720cce881a1f1e +size 305962 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index f29db004f..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce381c2d261b9b1ca61d8f6e2ff07b992283c327dc6b7cf53c7e5c9317abb7d3 -size 316443 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 284c3a270..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bfc23a95df8a88ac6e2777d67f381e800d23647c162a9a97131a101bbb97143 -size 306703 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index 5911faa72..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d3f58a108d933ec9ac0a5271af5b65d0a8ab9d521d54e48312b280cc42d71ac -size 322049 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index 020562673..03b9a37f7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291 +size 268504 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index 020562673..03b9a37f7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291 +size 268504 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 68d91fc43..a1d28a169 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441 -size 270622 +oid sha256:b5fa657236e12cbb2a8d2cd747029723a6b3829b475f28626d7647d7b2150918 +size 271579 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index 324bd9253..eba58870f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8 +size 284056 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index 324bd9253..eba58870f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8 +size 284056 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index 52bf2a163..de30e7574 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a -size 285370 +oid sha256:61ed5f4d77428be46357609d80a66e884dedbb8c255fdcc71d49eeba0eed2bf2 +size 285037 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png index 05be1395a..c56a90ad2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 -size 316544 +oid sha256:1cc2ef3cb819b5a82e0af32c3ab44aff0206530e291b00bdade58da2ebe4494a +size 308246 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png index d94d57759..c3ab7996d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d -size 317309 +oid sha256:575c8d81152642fa0eec0ea9901d1941fea58b7686cfaac1d01e0bf59f393c4b +size 308330 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png index e016e3de6..47616cd31 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63 -size 323979 +oid sha256:ba295a5ddb79bc61f0be9a28a636fdcc63055c26c46872d407fe20ff785f11ed +size 310415 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png new file mode 100644 index 000000000..9e8002ad1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9a87ef109c08411ca61d91ddcf010c272303a17abd90b6ba2204eac021055e5 +size 13665 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png new file mode 100644 index 000000000..45e770bd9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81496d88b42edf4b39ab723d0b5414b56140892f45d30fc2435904b630fa9af5 +size 13886 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png new file mode 100644 index 000000000..c84edd138 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:997e5281abd8cf3a587984ec1b7e31487ec5ddf16326d025124833d536e4ac27 +size 13910 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png deleted file mode 100644 index 8d99eb49b..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abfdd1e40c2c1d7fde419bda1da6e534ed989598e790b8ae4de35152a83f77a0 -size 13686 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png deleted file mode 100644 index bf93c39ff..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60c28eb1dc3c0416b20cec230917c0e4a70dd2929467bbab796ecbb04fe5a178 -size 13886 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png deleted file mode 100644 index 457298b54..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a523f097bf3b155f3823c5e400190b5d5e0d4470db7136576472c3257db76600 -size 13909 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png index 878a36a47..f288b3c8b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf -size 13432 +oid sha256:dd31b6fc59e1f9f88230d57b39362b76cedd0bd94e15904f69071ba3f465e48d +size 13656 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png index 02879b7a3..d2d2e3e4b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4ac8b88b317281738d833fc71f52348d9f4f45ea5a1303dd91fdb8b42be4267 -size 13186 +oid sha256:dd738ee2a397bb1ee305f03c70e185dea6f67827dc15b9df1966cfe8c0f28040 +size 13177 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png index ba0509480..c444923a2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1305d54f2139d4577490317051d6ce94a7fc8dd45b902d87a30fb04098dd4594 -size 13407 +oid sha256:2a2df64f89df17428415932c2ef0028d8ad408b5276264d99e6038b70473ebde +size 13417 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png index b16a5a5c7..74cbbd758 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3fc3a7ace123c330ea06072eb36dd5d65ed9154d4d0f55a828fc542c8a422c1 -size 13472 +oid sha256:234854be2a3f774a58baf79f20e68c7331b6caff486ab4b1e509a96e2a3d70b9 +size 13455 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png index 6adac16cf..a9bb2c316 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35757f2e0831cae2fbd3cc11ffaaae855e853ebaa9a1a5564b6568a5e1c442e9 -size 16031 +oid sha256:ef65ce360293ca5659730747087c15735c15df1143204acb60120a5b68cd7cd4 +size 15905 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png index 5d1030e6b..ceb918800 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82 -size 17520 +oid sha256:6618f169cf4b585979f8e9261af88fe4a61c3c40b453a159cb643cc062a6a9dc +size 17517 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png index 567e5d6a3..f6cb17367 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5 -size 18182 +oid sha256:4acf21f23978c83c9872bb2575ab45e4f0bbc86c8610c99479b1469fc12df5f2 +size 18112 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png index 10daff76b..fd565c0a1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72 -size 44622 +oid sha256:4ded8db323023a7c7620bba3b2259a549571442fe0a37883c7755ac69ae9d6d5 +size 44646 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png index 37e5035d8..c342e3a23 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3802cfe67638a24869d6cc9ace1d94460b4c0c26f2c91b12b95fa8f979de64bb -size 101579 +oid sha256:83c8403f5d0e5457721d992c1e6980134e8a65a1f646163a4f091cf34583ca02 +size 101417 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png index e72ea4b24..d07231c18 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf2021eba9edbb2295924f8394472ac0bb237f0c462c39aa32a2074ef15f9acc -size 81771 +oid sha256:e5412b892143bb433804c662750a64a1660b2072520db53d76ec6897c636ec50 +size 81742 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png index 0997945e5..7d2070820 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d11b18946d373b995ecbb449c8c4cfcc7078aad1c8705997bcbf83131acde03 -size 102439 +oid sha256:a88a48586502de786aca0b36341cf6033fb3ec3ce7924ce1e2819fd14791ffe4 +size 102235 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..79711e2eb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22920fb2379dee7d12fee52f6a39b8e46e1e99f77b91f879c51bb33a981dfdcb +size 98851 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..fa6d4cb43 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c7137e1b87d317d7e139cde8499deafa89f27bddba146cc5736f9c0566778c5 +size 81609 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 314a05606..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2236e81d33fcfb50afb9d5fd1a38c5ddf5d33fbb52de1c3204a4a9892fd334ce -size 99084 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 529304672..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4b59097d1507236af2556ae5f2638360b223b7752cd4c8f760bc14673d811d0 -size 81709 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index b51076bd1..0fe3d30bf 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a8d9c0d81525d9f37d2f36946939040aea30edfc2b7ec0bf329fb49f6c7d73f -size 69896 +oid sha256:aee197677c3276d4abb8fc027358b38be26462374e364841781626f0aa67e1a4 +size 69769 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png index 7204abff4..ef86e7c48 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4474b94e2d563938e10ec0526e7d94ba06b440db51b910604e752f7f9e814d66 -size 110757 +oid sha256:0b3c8dc7e653ef1846c7359e9a0f719bee91549846f160abb547cd0aab6a8a59 +size 110711 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png index 691623fc8..c65381c05 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58a61c1d9a1d05acd484948c3e5c0496dbc74c0060f5de71741de39eae04ffa8 -size 103875 +oid sha256:4b95721a963def9e82dd32e277ed4594213920d7808ad26696d01e4f8fda842e +size 103855 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png index e80e6c6e8..eb2c2a0c9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6649918c0394ead13c016a57b6a08561290651bccac88f7f15ba0e29dc5faa4 -size 110422 +oid sha256:cb174c104cdcf35433c98522a1d9d52ccf42e8927e0b59fec3556aeee8b15a47 +size 110505 From c9f7e6ea60c516b887256a32f47aee42a448fc21 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Apr 2026 23:19:38 +1000 Subject: [PATCH 118/240] Add ImageInfo.GetPixelMemorySize; docs & tests --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/ImageInfo.cs | 32 ++++++++++++++++++-- tests/ImageSharp.Tests/ImageInfoTests.cs | 30 ++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 78ceb0b23..3d32c7cda 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -468,7 +468,7 @@ internal sealed class GifDecoderCore : ImageDecoderCore int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3; this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate(768, AllocationOptions.Clean); stream.Read(this.currentLocalColorTable.GetSpan()[..length]); - rawColorTable = this.currentLocalColorTable!.GetSpan()[..length]; + rawColorTable = this.currentLocalColorTable.GetSpan()[..length]; } else if (this.globalColorTable != null) { diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index 0bbd73b63..d27c4b933 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -63,8 +63,12 @@ public class ImageInfo public int Height => this.Size.Height; /// - /// Gets the number of frames in the image. + /// Gets the number of frame metadata entries available for the image. /// + /// + /// This value is the same as count and may be 0 when frame + /// metadata was not populated by the decoder. + /// public int FrameCount => this.FrameMetadataCollection.Count; /// @@ -73,8 +77,12 @@ public class ImageInfo public ImageMetadata Metadata { get; } /// - /// Gets the collection of metadata associated with individual image frames. + /// Gets the metadata associated with the decoded image frames, if available. /// + /// + /// For multi-frame formats, decoders populate one entry per decoded frame. For single-frame formats, this + /// collection is typically empty. + /// public IReadOnlyList FrameMetadataCollection { get; } /// @@ -86,4 +94,24 @@ public class ImageInfo /// Gets the bounds of the image. /// public Rectangle Bounds => new(Point.Empty, this.Size); + + /// + /// Gets the total number of bytes required to store the image pixels in memory. + /// + /// + /// This reports the in-memory size of the pixel data represented by this , not the + /// encoded size of the image file. The value is computed from the image dimensions and + /// . When contains decoded frame metadata, the + /// per-frame size is multiplied by that count. Otherwise, the value is the in-memory size of the single + /// image frame represented by this . + /// + /// The total number of bytes required to store the image pixels in memory. + public long GetPixelMemorySize() + { + int count = this.FrameMetadataCollection.Count > 0 + ? this.FrameMetadataCollection.Count + : 1; + + return (long)this.Size.Width * this.Size.Height * (this.PixelType.BitsPerPixel / 8) * count; + } } diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 322b0af19..748c8a4f6 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -54,4 +54,34 @@ public class ImageInfoTests Assert.Equal(meta, info.Metadata); Assert.Equal(frameMetadata.Count, info.FrameMetadataCollection.Count); } + + [Fact] + public void GetPixelMemorySize_UsesSingleFrameWhenFrameMetadataIsEmpty() + { + const int width = 10; + const int height = 20; + + ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; + meta.GetPngMetadata(); + + ImageInfo info = new(new Size(width, height), meta); + + Assert.Equal(width * height * 4, info.GetPixelMemorySize()); + } + + [Fact] + public void GetPixelMemorySize_UsesFrameMetadataCountWhenAvailable() + { + const int width = 10; + const int height = 20; + IReadOnlyList frameMetadata = [new(), new(), new()]; + + ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; + meta.GetPngMetadata(); + + ImageInfo info = new(new Size(width, height), meta, frameMetadata); + + Assert.Equal(width * height * 4 * frameMetadata.Count, info.GetPixelMemorySize()); + } + } From d1623fee63d900eb0c061fc5372befc49d21cf9d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Apr 2026 18:02:06 +0200 Subject: [PATCH 119/240] Add tests for Rgb96 pixel conversions --- .../PixelFormats/Rgb96Tests.cs | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs new file mode 100644 index 000000000..7c6f6bebd --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs @@ -0,0 +1,308 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgb96Tests +{ + [Theory] + [InlineData(1, 2, 3)] + [InlineData(50, 70, 80)] + [InlineData(1000, 2000, 3000)] + public void Constructor(uint r, uint g, uint b) + { + Rgb96 p = new(r, g, b); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public void Rgb96_ToVector4() + { + Assert.Equal(Vector4.UnitW, new Rgb96(0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgb96(uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); + Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 1.0F), new Rgb96(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); + } + + [Theory] + [InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(uint.MaxValue / 2, 100, 2222)] + public void Rgb96_ToScaledVector4(uint r, uint g, uint b) + { + // arrange + Rgb96 rgb = new(r, g, b); + + float max = uint.MaxValue; + float rr = r / max; + float gg = g / max; + float bb = b / max; + + // act + Vector4 actual = rgb.ToScaledVector4(); + + // assert + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(1.0F, actual.W); + } + + [Theory] + [InlineData(0, 0, 0)] + [InlineData(5000, 100, 2222)] + public void Rgb96_FromScaledVector4(uint r, uint g, uint b) + { + // arrange + Rgb96 source = new(r, g, b); + Vector4 scaled = source.ToScaledVector4(); + + // act + Rgb96 actual = Rgb96.FromScaledVector4(scaled); + + // assert + Assert.Equal(source, actual); + } + + [Fact] + public void Rgb96_ToRgba32() + { + // arrange + Rgb96 rgb96 = new((uint)(uint.MaxValue * 0.1f), uint.MaxValue / 2, uint.MaxValue); + Rgba32 expected = new(25, 127, 255, 255); + + // act + Rgba32 actual = rgb96.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Equality_WhenTrue() + { + Rgb96 c1 = new(100, 2000, 3000); + Rgb96 c2 = new(100, 2000, 3000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Rgb96 c1 = new(100, 2000, 3000); + Rgb96 c2 = new(101, 2000, 3000); + Rgb96 c3 = new(100, 2000, 3001); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgb96_FromRgba32() + { + // arrange + Rgba32 source = new(25, 127, 255, 255); + Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgba32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgb24() + { + // arrange + Rgb24 source = new(25, 127, 255); + Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgb24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgb48() + { + // arrange + Rgb48 source = new(0, 32767, ushort.MaxValue); + Rgb96 expected = new(0, 2147450879, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgb48(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgba64() + { + // arrange + Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); + Rgb96 expected = new(0, 2147450879, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgba64(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromLa32() + { + // arrange + La32 source = new(32767, ushort.MaxValue); + Rgb96 expected = new(2147450879, 2147450879, 2147450879); + + // act + Rgb96 actual = Rgb96.FromLa32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromLa16() + { + // arrange + La16 source = new(127, byte.MaxValue); + Rgb96 expected = new(2139062143, 2139062143, 2139062143); + + // act + Rgb96 actual = Rgb96.FromLa16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromL16() + { + // arrange + L16 source = new(32767); + Rgb96 expected = new(2147450879, 2147450879, 2147450879); + + // act + Rgb96 actual = Rgb96.FromL16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromL8() + { + // arrange + L8 source = new(127); + Rgb96 expected = new(2139062143, 2139062143, 2139062143); + + // act + Rgb96 actual = Rgb96.FromL8(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgra32() + { + // arrange + Bgra32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgra32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgr24() + { + // arrange + Bgr24 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgr24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgra5551() + { + // arrange + Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); + Rgb96 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgra5551(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromArgb32() + { + // arrange + Argb32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromArgb32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromAbgr32() + { + // arrange + Abgr32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromAbgr32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_PixelInformation() + { + PixelTypeInfo info = Rgb96.GetPixelTypeInfo(); + Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); + Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB, info.ColorType); + + PixelComponentInfo componentInfo = info.ComponentInfo.Value; + Assert.Equal(3, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + Assert.Equal(32, componentInfo.GetComponentPrecision(0)); + Assert.Equal(32, componentInfo.GetComponentPrecision(1)); + Assert.Equal(32, componentInfo.GetComponentPrecision(2)); + Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); + } +} From 787ebb205d41b3a5dafa6f49d8d2d180c8ddb9f9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Apr 2026 18:03:24 +0200 Subject: [PATCH 120/240] Try fix issues with Rgb96 pixel conversions --- .../Common/Helpers/ColorNumerics.cs | 24 ++++++++-- .../PixelImplementations/Rgb96.cs | 44 +++++++++++++------ .../PixelImplementations/Rgba32.cs | 3 +- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 7a0e6d47a..735f6bc59 100644 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs @@ -12,8 +12,6 @@ namespace SixLabors.ImageSharp; /// internal static class ColorNumerics { - private const float Scale32Bit = 1f / 0xFFFFFFFF; - /// /// Vector for converting pixel to gray value as specified by /// ITU-R Recommendation BT.709. @@ -141,7 +139,7 @@ internal static class ColorNumerics /// The 32 bit component value. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte From32BitTo8Bit(uint component) => (byte)(component * Scale32Bit); + public static byte From32BitTo8Bit(uint component) => (byte)(component >> 24); /// /// Scales a value from an 8 bit to @@ -153,6 +151,26 @@ internal static class ColorNumerics public static ushort From8BitTo16Bit(byte component) => (ushort)(component * 257); + /// + /// Scales a value from an 16 bit to + /// an 16 bit equivalent. + /// + /// The 16 bit component value. + /// The 32 bit + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint From16BitTo32Bit(ushort component) + => (uint)(component * 65537); + + /// + /// Scales a value from an 8 bit to + /// an 32 bit equivalent. + /// + /// The 8 bit component value. + /// The 32 bit + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint From8BitTo32Bit(byte component) + => (uint)(component * 16843009); + /// /// Returns how many bits are required to store the specified number of colors. /// Performs a Log2() on the value. diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index d318b067c..5f61d5778 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -19,7 +19,7 @@ public partial struct Rgb96 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; - private const double Max = uint.MaxValue; + private const float Max = uint.MaxValue; /// /// Gets the red component. @@ -94,15 +94,19 @@ public partial struct Rgb96 : IPixel, IEquatable /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromVector4(Vector4 source) => FromVector4(source); + public static Rgb96 FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One) * Max; + return new Rgb96((uint)MathF.Round(source.X), (uint)MathF.Round(source.Y), (uint)MathF.Round(source.Z)); + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B); + public static Rgb96 FromAbgr32(Abgr32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromArgb32(Argb32 source) => new(source.R, source.G, source.B); + public static Rgb96 FromArgb32(Argb32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -110,47 +114,59 @@ public partial struct Rgb96 : IPixel, IEquatable /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B); + public static Rgb96 FromBgr24(Bgr24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B); + public static Rgb96 FromBgra32(Bgra32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb96 FromL8(L8 source) { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); + uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); return new Rgb96(rgb, rgb, rgb); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); + public static Rgb96 FromL16(L16 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); + return new(rgb, rgb, rgb); + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromLa16(La16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue); + public static Rgb96 FromLa16(La16 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); + return new(rgb, rgb, rgb); + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromLa32(La32 source) => new(source.L, source.L, source.L); + public static Rgb96 FromLa32(La32 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.L); + return new(rgb, rgb, rgb); + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B); + public static Rgb96 FromRgb24(Rgb24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B); + public static Rgb96 FromRgba32(Rgba32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B); + public static Rgb96 FromRgb48(Rgb48 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgb96 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B); + public static Rgb96 FromRgba64(Rgba64 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 491372447..72215dec0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -325,7 +325,8 @@ public partial struct Rgba32 : IPixel, IPackedVector { R = ColorNumerics.From32BitTo8Bit(source.R), G = ColorNumerics.From32BitTo8Bit(source.G), - B = ColorNumerics.From32BitTo8Bit(source.B) + B = ColorNumerics.From32BitTo8Bit(source.B), + A = byte.MaxValue }; /// From 0ec523f0689a4b022ab09eac4f911d7b99ec65e4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Apr 2026 18:21:03 +0200 Subject: [PATCH 121/240] Add null check for PixelComponentInfo in Rgb96_PixelInformation() --- tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs index 7c6f6bebd..7e6b8f55f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs @@ -297,7 +297,10 @@ public class Rgb96Tests Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); Assert.Equal(PixelColorType.RGB, info.ColorType); - PixelComponentInfo componentInfo = info.ComponentInfo.Value; + PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; + Assert.NotNull(maybeComponentInfo); + PixelComponentInfo componentInfo = maybeComponentInfo.Value; + Assert.Equal(3, componentInfo.ComponentCount); Assert.Equal(0, componentInfo.Padding); Assert.Equal(32, componentInfo.GetComponentPrecision(0)); From 1c5e3e1a69d89d0202185100166c6a4c1339bd3a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 8 Apr 2026 14:39:56 +1000 Subject: [PATCH 122/240] Modernize base PorterDuffFunctions --- src/ImageSharp/Common/Helpers/Numerics.cs | 2 +- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 37 +++--------- .../Common/Helpers/Vector256Utilities.cs | 22 +++++++ .../PixelBlenders/PorterDuffFunctions.cs | 57 ++++++++++--------- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index efe68977b..513eb7ab1 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -690,7 +690,7 @@ internal static class Numerics /// /// The span of vectors [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CubePowOnXYZ(Span vectors) + public static void CubePowOnXYZ(Span vectors) { ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); ref Vector4 endRef = ref Unsafe.Add(ref baseRef, (uint)vectors.Length); diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 076590605..154f0b5e2 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -602,48 +602,25 @@ internal static partial class SimdUtils } /// - /// Performs a multiplication and an addition of the . - /// TODO: Fix. The arguments are in a different order to the FMA intrinsic. + /// Performs a multiplication and a negated addition of the . /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. + /// ret = va - (vm0 * vm1) + /// The vector to add to the negated intermediate result. /// The first vector to multiply. /// The second vector to multiply. /// The . - [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 MultiplyAdd( + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector256 MultiplyAddNegated( Vector256 va, Vector256 vm0, Vector256 vm1) { if (Fma.IsSupported) { - return Fma.MultiplyAdd(vm1, vm0, va); - } - - return va + (vm0 * vm1); - } - - /// - /// Performs a multiplication and a negated addition of the . - /// - /// ret = c - (a * b) - /// The first vector to multiply. - /// The second vector to multiply. - /// The vector to add negated to the intermediate result. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector256 MultiplyAddNegated( - Vector256 a, - Vector256 b, - Vector256 c) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAddNegated(a, b, c); + return Fma.MultiplyAddNegated(vm0, vm1, va); } - return Avx.Subtract(c, Avx.Multiply(a, b)); + return Avx.Subtract(va, Avx.Multiply(vm0, vm1)); } /// diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs index 14ac13dd8..90e3169b3 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -115,6 +115,28 @@ internal static class Vector256_ return va + (vm0 * vm1); } + /// + /// Performs a multiplication and a negated addition of the . + /// + /// ret = va - (vm0 * vm1) + /// The vector to add to the negated intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector256 MultiplyAddNegated( + Vector256 va, + Vector256 vm0, + Vector256 vm1) + { + if (Fma.IsSupported) + { + return Fma.MultiplyAddNegated(vm0, vm1, va); + } + + return va - (vm0 * vm1); + } + /// /// Performs a multiplication and a subtraction of the . /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index ca358be31..45c4aade7 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -5,6 +5,7 @@ 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; @@ -62,7 +63,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Multiply(Vector256 backdrop, Vector256 source) - => Avx.Multiply(backdrop, source); + => backdrop * source; /// /// Returns the result of the "Add" compositing equation. @@ -82,7 +83,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Add(Vector256 backdrop, Vector256 source) - => Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source)); + => Vector256.Min(Vector256.Create(1F), backdrop + source); /// /// Returns the result of the "Subtract" compositing equation. @@ -102,7 +103,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Subtract(Vector256 backdrop, Vector256 source) - => Avx.Max(Vector256.Zero, Avx.Subtract(backdrop, source)); + => Vector256.Max(Vector256.Zero, backdrop - source); /// /// Returns the result of the "Screen" compositing equation. @@ -124,7 +125,7 @@ internal static partial class PorterDuffFunctions public static Vector256 Screen(Vector256 backdrop, Vector256 source) { Vector256 vOne = Vector256.Create(1F); - return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne); + return Vector256_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source); } /// @@ -145,7 +146,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Darken(Vector256 backdrop, Vector256 source) - => Avx.Min(backdrop, source); + => Vector256.Min(backdrop, source); /// /// Returns the result of the "Lighten" compositing equation. @@ -164,7 +165,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Lighten(Vector256 backdrop, Vector256 source) - => Avx.Max(backdrop, source); + => Vector256.Max(backdrop, source); /// /// Returns the result of the "Overlay" compositing equation. @@ -192,7 +193,7 @@ internal static partial class PorterDuffFunctions public static Vector256 Overlay(Vector256 backdrop, Vector256 source) { Vector256 color = OverlayValueFunction(backdrop, source); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); } /// @@ -221,7 +222,7 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLight(Vector256 backdrop, Vector256 source) { Vector256 color = OverlayValueFunction(source, backdrop); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); } /// @@ -244,10 +245,10 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayValueFunction(Vector256 backdrop, Vector256 source) { Vector256 vOne = Vector256.Create(1F); - Vector256 left = Avx.Multiply(Avx.Add(backdrop, backdrop), source); + Vector256 left = (backdrop + backdrop) * source; Vector256 vOneMinusSource = Avx.Subtract(vOne, source); - Vector256 right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne); + Vector256 right = Vector256_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop); Vector256 cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F)); return Avx.BlendVariable(left, right, cmp); } @@ -295,17 +296,17 @@ internal static partial class PorterDuffFunctions Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); Vector256 dW = Avx.Permute(destination, ShuffleAlphaControl); - Vector256 blendW = Avx.Multiply(sW, dW); - Vector256 dstW = Avx.Subtract(dW, blendW); - Vector256 srcW = Avx.Subtract(sW, blendW); + Vector256 blendW = sW * dW; + Vector256 dstW = dW - blendW; + Vector256 srcW = sW - blendW; // calculate final alpha - Vector256 alpha = Avx.Add(dstW, sW); + Vector256 alpha = dstW + sW; // calculate final color - Vector256 color = Avx.Multiply(destination, dstW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW); + Vector256 color = destination * dstW; + color = Vector256_.MultiplyAdd(color, source, srcW); + color = Vector256_.MultiplyAdd(color, blend, blendW); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -354,11 +355,11 @@ internal static partial class PorterDuffFunctions // calculate weights Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); - Vector256 blendW = Avx.Multiply(sW, alpha); - Vector256 dstW = Avx.Subtract(alpha, blendW); + Vector256 blendW = sW * alpha; + Vector256 dstW = alpha - blendW; // calculate final color - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); + Vector256 color = Vector256_.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -392,10 +393,10 @@ internal static partial class PorterDuffFunctions public static Vector256 In(Vector256 destination, Vector256 source) { // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl); + Vector256 alpha = Avx.Permute(source * destination, ShuffleAlphaControl); // premultiply - Vector256 color = Avx.Multiply(source, alpha); + Vector256 color = source * alpha; // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -429,10 +430,10 @@ internal static partial class PorterDuffFunctions public static Vector256 Out(Vector256 destination, Vector256 source) { // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl); + Vector256 alpha = Avx.Permute(source * (Vector256.Create(1F) - destination), ShuffleAlphaControl); // premultiply - Vector256 color = Avx.Multiply(source, alpha); + Vector256 color = source * alpha; // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -475,12 +476,12 @@ internal static partial class PorterDuffFunctions Vector256 dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl); Vector256 vOne = Vector256.Create(1F); - Vector256 srcW = Avx.Subtract(vOne, dW); - Vector256 dstW = Avx.Subtract(vOne, sW); + Vector256 srcW = vOne - dW; + Vector256 dstW = vOne - sW; // calculate alpha - Vector256 alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW); - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW); + 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); // unpremultiply return Numerics.UnPremultiply(color, alpha); From 5e4f3ef1ffb738240ccffda755d5d1be3ab80385 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 8 Apr 2026 14:48:45 +1000 Subject: [PATCH 123/240] Use operators in generated functions --- .../PorterDuffFunctions.Generated.cs | 198 +++++++++--------- .../PorterDuffFunctions.Generated.tt | 22 +- .../FeatureTesting/FeatureTestRunner.cs | 2 + 3 files changed, 112 insertions(+), 110 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 255bafc79..f0635230c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -37,7 +37,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "NormalSrcAtop" compositing equation. @@ -64,7 +64,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Normal(backdrop, source)); } @@ -94,7 +94,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Normal(backdrop, source)); } @@ -123,7 +123,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "NormalSrcOut" compositing equation. @@ -149,7 +149,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "NormalDest" compositing equation. @@ -202,7 +202,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Normal(source, backdrop)); } @@ -232,7 +232,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Normal(source, backdrop)); } @@ -261,7 +261,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "NormalDestOut" compositing equation. @@ -287,7 +287,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "NormalXor" compositing equation. @@ -313,7 +313,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "NormalClear" compositing equation. @@ -339,7 +339,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -558,7 +558,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "MultiplySrcAtop" compositing equation. @@ -585,7 +585,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Multiply(backdrop, source)); } @@ -615,7 +615,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Multiply(backdrop, source)); } @@ -644,7 +644,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "MultiplySrcOut" compositing equation. @@ -670,7 +670,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "MultiplyDest" compositing equation. @@ -723,7 +723,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Multiply(source, backdrop)); } @@ -753,7 +753,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Multiply(source, backdrop)); } @@ -782,7 +782,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "MultiplyDestOut" compositing equation. @@ -808,7 +808,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "MultiplyXor" compositing equation. @@ -834,7 +834,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "MultiplyClear" compositing equation. @@ -860,7 +860,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -1079,7 +1079,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "AddSrcAtop" compositing equation. @@ -1106,7 +1106,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Add(backdrop, source)); } @@ -1136,7 +1136,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Add(backdrop, source)); } @@ -1165,7 +1165,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "AddSrcOut" compositing equation. @@ -1191,7 +1191,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "AddDest" compositing equation. @@ -1244,7 +1244,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Add(source, backdrop)); } @@ -1274,7 +1274,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Add(source, backdrop)); } @@ -1303,7 +1303,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "AddDestOut" compositing equation. @@ -1329,7 +1329,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "AddXor" compositing equation. @@ -1355,7 +1355,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "AddClear" compositing equation. @@ -1381,7 +1381,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -1600,7 +1600,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "SubtractSrcAtop" compositing equation. @@ -1627,7 +1627,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Subtract(backdrop, source)); } @@ -1657,7 +1657,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Subtract(backdrop, source)); } @@ -1686,7 +1686,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "SubtractSrcOut" compositing equation. @@ -1712,7 +1712,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "SubtractDest" compositing equation. @@ -1765,7 +1765,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Subtract(source, backdrop)); } @@ -1795,7 +1795,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Subtract(source, backdrop)); } @@ -1824,7 +1824,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "SubtractDestOut" compositing equation. @@ -1850,7 +1850,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "SubtractXor" compositing equation. @@ -1876,7 +1876,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "SubtractClear" compositing equation. @@ -1902,7 +1902,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -2121,7 +2121,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "ScreenSrcAtop" compositing equation. @@ -2148,7 +2148,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Screen(backdrop, source)); } @@ -2178,7 +2178,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Screen(backdrop, source)); } @@ -2207,7 +2207,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "ScreenSrcOut" compositing equation. @@ -2233,7 +2233,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "ScreenDest" compositing equation. @@ -2286,7 +2286,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Screen(source, backdrop)); } @@ -2316,7 +2316,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Screen(source, backdrop)); } @@ -2345,7 +2345,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "ScreenDestOut" compositing equation. @@ -2371,7 +2371,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "ScreenXor" compositing equation. @@ -2397,7 +2397,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "ScreenClear" compositing equation. @@ -2423,7 +2423,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -2642,7 +2642,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "DarkenSrcAtop" compositing equation. @@ -2669,7 +2669,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Darken(backdrop, source)); } @@ -2699,7 +2699,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Darken(backdrop, source)); } @@ -2728,7 +2728,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "DarkenSrcOut" compositing equation. @@ -2754,7 +2754,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "DarkenDest" compositing equation. @@ -2807,7 +2807,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Darken(source, backdrop)); } @@ -2837,7 +2837,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Darken(source, backdrop)); } @@ -2866,7 +2866,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "DarkenDestOut" compositing equation. @@ -2892,7 +2892,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "DarkenXor" compositing equation. @@ -2918,7 +2918,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "DarkenClear" compositing equation. @@ -2944,7 +2944,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -3163,7 +3163,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "LightenSrcAtop" compositing equation. @@ -3190,7 +3190,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Lighten(backdrop, source)); } @@ -3220,7 +3220,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Lighten(backdrop, source)); } @@ -3249,7 +3249,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "LightenSrcOut" compositing equation. @@ -3275,7 +3275,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "LightenDest" compositing equation. @@ -3328,7 +3328,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Lighten(source, backdrop)); } @@ -3358,7 +3358,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Lighten(source, backdrop)); } @@ -3387,7 +3387,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "LightenDestOut" compositing equation. @@ -3413,7 +3413,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "LightenXor" compositing equation. @@ -3439,7 +3439,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "LightenClear" compositing equation. @@ -3465,7 +3465,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -3684,7 +3684,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "OverlaySrcAtop" compositing equation. @@ -3711,7 +3711,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, Overlay(backdrop, source)); } @@ -3741,7 +3741,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Overlay(backdrop, source)); } @@ -3770,7 +3770,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "OverlaySrcOut" compositing equation. @@ -3796,7 +3796,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "OverlayDest" compositing equation. @@ -3849,7 +3849,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, Overlay(source, backdrop)); } @@ -3879,7 +3879,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, Overlay(source, backdrop)); } @@ -3908,7 +3908,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "OverlayDestOut" compositing equation. @@ -3934,7 +3934,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "OverlayXor" compositing equation. @@ -3960,7 +3960,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "OverlayClear" compositing equation. @@ -3986,7 +3986,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// @@ -4205,7 +4205,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "HardLightSrcAtop" compositing equation. @@ -4232,7 +4232,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, HardLight(backdrop, source)); } @@ -4262,7 +4262,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, HardLight(backdrop, source)); } @@ -4291,7 +4291,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "HardLightSrcOut" compositing equation. @@ -4317,7 +4317,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "HardLightDest" compositing equation. @@ -4370,7 +4370,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, HardLight(source, backdrop)); } @@ -4400,7 +4400,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, HardLight(source, backdrop)); } @@ -4429,7 +4429,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "HardLightDestOut" compositing equation. @@ -4455,7 +4455,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "HardLightXor" compositing equation. @@ -4481,7 +4481,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "HardLightClear" compositing equation. @@ -4507,7 +4507,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 150adb33a..83bc055ef 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -47,7 +47,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Src(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); /// /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. @@ -74,7 +74,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(backdrop, source, <#=blender#>(backdrop, source)); } @@ -104,7 +104,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, <#=blender#>(backdrop, source)); } @@ -133,7 +133,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "<#=blender#>SrcOut" compositing equation. @@ -159,7 +159,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "<#=blender#>Dest" compositing equation. @@ -212,7 +212,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Atop(source, backdrop, <#=blender#>(source, backdrop)); } @@ -242,7 +242,7 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(source, backdrop, <#=blender#>(source, backdrop)); } @@ -271,7 +271,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "<#=blender#>DestOut" compositing equation. @@ -297,7 +297,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// /// Returns the result of the "<#=blender#>Xor" compositing equation. @@ -323,7 +323,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Xor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// /// Returns the result of the "<#=blender#>Clear" compositing equation. @@ -349,7 +349,7 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Clear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); <#} #> diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index d3671abd4..be3e9ccd5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -455,6 +455,7 @@ public enum HwIntrinsics : long DisableVAES = 1L << 17, DisableWAITPKG = 1L << 18, DisableX86Serialize = 1 << 19, + // Arm64 DisableArm64Aes = 1L << 20, DisableArm64Atomics = 1L << 21, @@ -466,6 +467,7 @@ public enum HwIntrinsics : long DisableArm64Sha256 = 1L << 27, DisableArm64Sve = 1L << 28, DisableArm64Sve2 = 1L << 29, + // RISC-V64 DisableRiscV64Zba = 1L << 30, DisableRiscV64Zbb = 1L << 31, From fd688db0eb1c011a2b41e879c6b83ba843b9136c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 8 Apr 2026 15:32:55 +1000 Subject: [PATCH 124/240] Complete implementation and add tests/benchmark --- src/ImageSharp/Common/Helpers/Numerics.cs | 14 + .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 22 - .../Common/Helpers/Vector512Utilities.cs | 15 + .../DefaultPixelBlenders.Generated.cs | 17056 +++++++++++++++- .../DefaultPixelBlenders.Generated.tt | 156 +- .../PorterDuffFunctions.Generated.cs | 1370 +- .../PorterDuffFunctions.Generated.tt | 150 + .../PixelBlenders/PorterDuffFunctions.cs | 242 + .../PorterDuffBulkVsSingleVector.cs | 23 +- .../PorterDuffCompositorTests.cs | 2 +- .../PixelBlenders/PorterDuffFunctionsTests.cs | 152 +- .../PorterDuffFunctionsTestsTPixel.cs | 108 +- .../TestUtilities/ApproximateFloatComparer.cs | 18 +- 13 files changed, 18723 insertions(+), 605 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 513eb7ab1..04ed48e21 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -643,6 +643,20 @@ internal static class Numerics return Avx.Blend(result, alpha, BlendAlphaControl); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 UnPremultiply(Vector512 source, Vector512 alpha) + { + // Check if alpha is zero to avoid division by zero + 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 + 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); + } + /// /// Permutes the given vector return a new instance with all the values set to . /// diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 154f0b5e2..022056deb 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -601,28 +601,6 @@ internal static partial class SimdUtils } } - /// - /// Performs a multiplication and a negated addition of the . - /// - /// ret = va - (vm0 * vm1) - /// The vector to add to the negated intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector256 MultiplyAddNegated( - Vector256 va, - Vector256 vm0, - Vector256 vm1) - { - if (Fma.IsSupported) - { - return Fma.MultiplyAddNegated(vm0, vm1, va); - } - - return Avx.Subtract(va, Avx.Multiply(vm0, vm1)); - } - /// /// Blend packed 8-bit integers from and using . /// The high bit of each corresponding byte determines the selection. diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs index 03ee4626c..82a20158a 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -87,6 +87,21 @@ internal static class Vector512_ Vector512 vm1) => Avx512F.FusedMultiplyAdd(vm0, vm1, va); + /// + /// Performs a multiplication and a negated addition of the . + /// + /// ret = va - (vm0 * vm1) + /// The vector to add to the negated intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyAddNegated( + Vector512 va, + Vector512 vm0, + Vector512 vm1) + => Avx512F.FusedMultiplyAddNegated(vm0, vm1, va); + /// /// Restricts a vector between a minimum and a maximum value. /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 7cd9cc57a..883693031 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -46,7 +46,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -85,7 +112,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -121,7 +178,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -168,7 +269,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -233,7 +381,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -272,7 +447,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -308,7 +513,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -355,7 +604,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -420,7 +716,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -459,7 +782,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -495,7 +848,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -542,7 +939,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -607,7 +1051,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -646,7 +1117,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -682,7 +1183,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -729,7 +1274,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -794,15 +1386,15 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); - 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); + 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)) { @@ -812,9 +1404,36 @@ internal static class DefaultPixelBlenders sourceBase = ref Unsafe.Add(ref sourceBase, 1); } - if (Numerics.Modulo2(destination.Length) != 0) + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } @@ -833,7 +1452,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -869,7 +1518,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -916,7 +1609,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -981,7 +1721,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1020,7 +1787,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1056,7 +1853,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1103,7 +1944,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1168,7 +2056,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1207,7 +2122,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1243,7 +2188,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1290,7 +2279,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1355,7 +2391,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1394,7 +2457,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1430,7 +2523,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1477,7 +2614,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1542,7 +2726,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1581,7 +2792,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1617,7 +2858,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1664,7 +2949,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1729,7 +3061,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1768,7 +3127,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1804,7 +3193,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1851,7 +3284,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1916,7 +3396,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1955,7 +3462,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -1991,7 +3528,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2038,7 +3619,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2103,7 +3731,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2142,7 +3797,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2178,7 +3863,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2225,7 +3954,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2290,7 +4066,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2329,7 +4132,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2365,7 +4198,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2412,7 +4289,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2477,7 +4401,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2516,7 +4467,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2552,7 +4533,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2599,7 +4624,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2664,7 +4736,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2703,7 +4802,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2739,7 +4868,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2786,7 +4959,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2851,7 +5071,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2890,7 +5137,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2926,7 +5203,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -2960,20 +5281,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -3038,7 +5406,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3077,7 +5472,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3113,7 +5538,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3160,7 +5629,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3225,7 +5741,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3264,7 +5807,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3300,7 +5873,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3347,7 +5964,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3412,7 +6076,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3451,7 +6142,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3487,7 +6208,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3534,7 +6299,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3599,7 +6411,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3638,7 +6477,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3674,7 +6543,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3721,7 +6634,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3786,7 +6746,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3825,7 +6812,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3861,7 +6878,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3908,7 +6969,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -3973,7 +7081,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4012,7 +7147,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4048,7 +7213,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4095,7 +7304,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4160,7 +7416,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4199,7 +7482,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4235,7 +7548,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4282,7 +7639,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4347,7 +7751,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4386,7 +7817,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4422,7 +7883,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4461,15 +7966,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -4534,7 +8086,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4573,7 +8152,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4609,7 +8218,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4656,7 +8309,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4721,7 +8421,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4760,7 +8487,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4796,7 +8553,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4843,7 +8644,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4908,7 +8756,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4947,7 +8822,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -4983,7 +8888,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5030,7 +8979,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5095,7 +9091,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5134,7 +9157,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5170,7 +9223,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5217,7 +9314,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5282,7 +9426,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5321,7 +9492,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5357,7 +9558,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5404,7 +9649,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5469,7 +9761,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5508,7 +9827,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5544,7 +9893,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5591,7 +9984,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5656,7 +10096,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5695,7 +10162,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5731,7 +10228,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5778,7 +10319,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5843,7 +10431,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5882,7 +10497,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5918,7 +10563,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -5952,20 +10641,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -6030,7 +10766,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6069,7 +10832,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6105,7 +10898,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6152,7 +10989,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6217,7 +11101,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6256,7 +11167,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6292,7 +11233,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6339,7 +11324,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6404,7 +11436,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6443,7 +11502,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6479,7 +11568,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6526,7 +11659,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6591,7 +11771,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6630,7 +11837,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6666,7 +11903,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6713,7 +11994,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6778,7 +12106,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6817,7 +12172,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6853,7 +12238,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6900,7 +12329,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -6965,7 +12441,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7004,7 +12507,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7040,7 +12573,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7087,7 +12664,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7152,7 +12776,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7191,7 +12842,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7227,7 +12908,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7274,7 +12999,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7339,7 +13111,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7378,7 +13177,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7414,7 +13243,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7453,15 +13326,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -7526,7 +13446,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7565,7 +13512,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7601,7 +13578,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7648,7 +13669,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7713,7 +13781,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7752,7 +13847,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7788,7 +13913,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7835,7 +14004,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7900,7 +14116,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7939,7 +14182,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -7975,7 +14248,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8022,7 +14339,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8087,7 +14451,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8126,7 +14517,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8162,7 +14583,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8209,7 +14674,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8274,7 +14786,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8313,7 +14852,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8349,7 +14918,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8396,7 +15009,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8461,7 +15121,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8500,7 +15187,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8536,7 +15253,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8583,7 +15344,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8648,7 +15456,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8687,7 +15522,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8723,7 +15588,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8770,7 +15679,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8835,7 +15791,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8874,7 +15857,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8910,7 +15923,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -8944,20 +16001,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -9022,7 +16126,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9061,7 +16192,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9097,7 +16258,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9144,7 +16349,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9209,7 +16461,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9248,7 +16527,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9284,7 +16593,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9331,7 +16684,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9396,7 +16796,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9435,7 +16862,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9471,7 +16928,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9518,7 +17019,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9583,7 +17131,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9622,7 +17197,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9658,7 +17263,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9705,7 +17354,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9770,7 +17466,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9809,7 +17532,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9845,7 +17598,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9892,7 +17689,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9957,7 +17801,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -9996,7 +17867,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10032,7 +17933,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10079,7 +18024,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10144,7 +18136,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10183,7 +18202,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10219,7 +18268,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10266,7 +18359,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10331,7 +18471,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10370,7 +18537,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10406,7 +18603,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10445,15 +18686,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -10518,7 +18806,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10557,7 +18872,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10593,7 +18938,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10640,7 +19029,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10705,7 +19141,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10744,7 +19207,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10780,7 +19273,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10827,7 +19364,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10892,7 +19476,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10931,7 +19542,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -10967,7 +19608,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11014,7 +19699,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11079,7 +19811,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11118,7 +19877,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11154,7 +19943,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11201,7 +20034,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11266,7 +20146,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11305,7 +20212,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11341,7 +20278,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11388,7 +20369,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11453,7 +20481,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11492,7 +20547,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11528,7 +20613,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11575,7 +20704,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11640,7 +20816,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11679,7 +20882,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11715,7 +20948,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11762,7 +21039,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11827,7 +21151,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11866,7 +21217,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11902,7 +21283,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -11936,20 +21361,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -12014,7 +21486,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12053,7 +21552,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12089,7 +21618,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12136,7 +21709,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12201,7 +21821,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12240,7 +21887,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12276,7 +21953,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12323,7 +22044,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12388,7 +22156,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12427,7 +22222,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12463,7 +22288,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12510,7 +22379,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12575,7 +22491,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12614,7 +22557,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12650,7 +22623,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12697,7 +22714,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12762,7 +22826,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12801,7 +22892,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12837,7 +22958,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12884,7 +23049,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12949,7 +23161,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -12988,7 +23227,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13024,7 +23293,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13071,7 +23384,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13136,7 +23496,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13175,7 +23562,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13211,7 +23628,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13258,7 +23719,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13323,7 +23831,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13362,7 +23897,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13398,7 +23963,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13437,15 +24046,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -13510,7 +24166,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13549,7 +24232,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13585,7 +24298,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13632,7 +24389,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13697,7 +24501,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13736,7 +24567,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13772,7 +24633,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13819,7 +24724,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13884,7 +24836,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13923,7 +24902,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -13959,7 +24968,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14006,7 +25059,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14071,7 +25171,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14110,7 +25237,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14146,7 +25303,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14193,7 +25394,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14258,7 +25506,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14297,7 +25572,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14333,7 +25638,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14380,7 +25729,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14445,7 +25841,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14484,7 +25907,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14520,7 +25973,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14567,7 +26064,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14632,7 +26176,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14671,7 +26242,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14707,7 +26308,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14754,7 +26399,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14819,7 +26511,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14858,7 +26577,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14894,7 +26643,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -14928,20 +26721,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -15006,7 +26846,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15045,7 +26912,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15081,7 +26978,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15128,7 +27069,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15193,7 +27181,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15232,7 +27247,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15268,7 +27313,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15315,7 +27404,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15380,7 +27516,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15419,7 +27582,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15455,7 +27648,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15502,7 +27739,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15567,7 +27851,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15606,7 +27917,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15642,7 +27983,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15689,7 +28074,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15754,7 +28186,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15793,7 +28252,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15829,7 +28318,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15876,7 +28409,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15941,7 +28521,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -15980,7 +28587,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16016,7 +28653,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16063,7 +28744,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16128,7 +28856,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16167,7 +28922,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16203,7 +28988,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16250,7 +29079,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16315,7 +29191,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16354,7 +29257,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16390,7 +29323,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16429,15 +29406,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -16502,7 +29526,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16541,7 +29592,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16577,7 +29658,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16624,7 +29749,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16689,7 +29861,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16728,7 +29927,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16764,7 +29993,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16811,7 +30084,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16876,7 +30196,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16915,7 +30262,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16951,7 +30328,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -16998,7 +30419,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17063,7 +30531,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17102,7 +30597,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17138,7 +30663,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17185,7 +30754,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17250,7 +30866,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17289,7 +30932,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17325,7 +30998,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17372,7 +31089,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17437,7 +31201,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17476,7 +31267,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17512,7 +31333,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17559,7 +31424,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17624,7 +31536,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17663,7 +31602,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17699,7 +31668,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17746,7 +31759,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17811,7 +31871,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17850,7 +31937,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17886,7 +32003,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -17920,20 +32081,67 @@ internal static class DefaultPixelBlenders int i = destination.Length - 1; destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } - } - else - { - for (int i = 0; i < destination.Length; i++) + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -17998,7 +32206,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18037,7 +32272,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18073,7 +32338,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18120,7 +32429,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18185,7 +32541,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18224,7 +32607,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18260,7 +32673,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18307,7 +32764,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18372,7 +32876,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18411,7 +32942,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18447,7 +33008,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18494,7 +33099,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18559,7 +33211,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18598,7 +33277,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18634,7 +33343,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18681,7 +33434,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18746,7 +33546,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18785,7 +33612,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18821,7 +33678,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18868,7 +33769,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18933,7 +33881,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -18972,7 +33947,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19008,7 +34013,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19055,7 +34104,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19120,7 +34216,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19159,7 +34282,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19195,7 +34348,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19242,7 +34439,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19307,7 +34551,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19346,7 +34617,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19382,7 +34683,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19421,15 +34766,62 @@ internal static class DefaultPixelBlenders { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } } } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -19494,7 +34886,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19533,7 +34952,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19569,7 +35018,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19616,7 +35109,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19681,7 +35221,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19720,7 +35287,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19756,7 +35353,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19803,7 +35444,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19868,7 +35556,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19907,7 +35622,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19943,7 +35688,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -19990,7 +35779,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -20055,7 +35891,34 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -20094,7 +35957,37 @@ internal static class DefaultPixelBlenders { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -20130,7 +36023,51 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); @@ -20177,7 +36114,54 @@ internal static class DefaultPixelBlenders /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 3b885826b..c2439c24c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -89,7 +89,34 @@ var blenders = new []{ { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -128,7 +155,37 @@ var blenders = new []{ { amount = Numerics.Clamp(amount, 0, 1); - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -164,7 +221,51 @@ var blenders = new []{ /// protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); @@ -211,7 +312,54 @@ var blenders = new []{ /// protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { - if (Avx2.IsSupported && destination.Length >= 2) + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 + ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) { // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index f0635230c..d32966c24 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -39,6 +39,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "NormalSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "NormalSrcAtop" compositing equation. /// @@ -69,6 +80,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Normal(backdrop, source)); } + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Normal(backdrop, source)); + } + /// /// Returns the result of the "NormalSrcOver" compositing equation. /// @@ -99,6 +125,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Normal(backdrop, source)); } + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Normal(backdrop, source)); + } + /// /// Returns the result of the "NormalSrcIn" compositing equation. /// @@ -125,6 +166,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "NormalSrcOut" compositing equation. /// @@ -151,6 +203,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "NormalDest" compositing equation. /// @@ -177,6 +240,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "NormalDestAtop" compositing equation. /// @@ -207,6 +283,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Normal(source, backdrop)); } + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Normal(source, backdrop)); + } + /// /// Returns the result of the "NormalDestOver" compositing equation. /// @@ -237,6 +328,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Normal(source, backdrop)); } + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Normal(source, backdrop)); + } + /// /// Returns the result of the "NormalDestIn" compositing equation. /// @@ -263,6 +369,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "NormalDestOut" compositing equation. /// @@ -289,6 +406,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "NormalXor" compositing equation. /// @@ -315,6 +443,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "NormalClear" compositing equation. /// @@ -341,6 +480,17 @@ internal static partial class PorterDuffFunctions public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "NormalSrc" compositing equation. @@ -560,6 +710,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "MultiplySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "MultiplySrcAtop" compositing equation. /// @@ -590,6 +751,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Multiply(backdrop, source)); } + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + /// /// Returns the result of the "MultiplySrcOver" compositing equation. /// @@ -620,6 +796,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Multiply(backdrop, source)); } + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Multiply(backdrop, source)); + } + /// /// Returns the result of the "MultiplySrcIn" compositing equation. /// @@ -646,6 +837,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "MultiplySrcOut" compositing equation. /// @@ -672,6 +874,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "MultiplyDest" compositing equation. /// @@ -698,6 +911,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "MultiplyDestAtop" compositing equation. /// @@ -728,6 +954,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Multiply(source, backdrop)); } + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + /// /// Returns the result of the "MultiplyDestOver" compositing equation. /// @@ -758,6 +999,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Multiply(source, backdrop)); } + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Multiply(source, backdrop)); + } + /// /// Returns the result of the "MultiplyDestIn" compositing equation. /// @@ -784,6 +1040,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "MultiplyDestOut" compositing equation. /// @@ -810,6 +1077,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "MultiplyDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "MultiplyXor" compositing equation. /// @@ -836,6 +1114,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "MultiplyClear" compositing equation. /// @@ -862,6 +1151,17 @@ internal static partial class PorterDuffFunctions public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "MultiplySrc" compositing equation. @@ -1081,6 +1381,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "AddSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "AddSrcAtop" compositing equation. /// @@ -1111,6 +1422,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Add(backdrop, source)); } + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Add(backdrop, source)); + } + /// /// Returns the result of the "AddSrcOver" compositing equation. /// @@ -1142,18 +1468,33 @@ internal static partial class PorterDuffFunctions } /// - /// Returns the result of the "AddSrcIn" compositing equation. + /// Returns the result of the "AddSrcOver" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + public static Vector512 AddSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) { - source = Numerics.WithW(source, source * opacity); + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); - return In(backdrop, source); + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); } /// @@ -1167,6 +1508,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "AddSrcOut" compositing equation. /// @@ -1193,6 +1545,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "AddDest" compositing equation. /// @@ -1219,6 +1582,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "AddDestAtop" compositing equation. /// @@ -1249,6 +1625,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Add(source, backdrop)); } + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Add(source, backdrop)); + } + /// /// Returns the result of the "AddDestOver" compositing equation. /// @@ -1279,6 +1670,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Add(source, backdrop)); } + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Add(source, backdrop)); + } + /// /// Returns the result of the "AddDestIn" compositing equation. /// @@ -1305,6 +1711,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "AddDestOut" compositing equation. /// @@ -1331,6 +1748,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "AddXor" compositing equation. /// @@ -1357,6 +1785,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "AddClear" compositing equation. /// @@ -1383,6 +1822,17 @@ internal static partial class PorterDuffFunctions public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "AddSrc" compositing equation. @@ -1602,6 +2052,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "SubtractSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "SubtractSrcAtop" compositing equation. /// @@ -1632,6 +2093,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Subtract(backdrop, source)); } + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + /// /// Returns the result of the "SubtractSrcOver" compositing equation. /// @@ -1662,6 +2138,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Subtract(backdrop, source)); } + /// + /// Returns the result of the "SubtractSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Subtract(backdrop, source)); + } + /// /// Returns the result of the "SubtractSrcIn" compositing equation. /// @@ -1688,6 +2179,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "SubtractSrcOut" compositing equation. /// @@ -1714,6 +2216,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "SubtractDest" compositing equation. /// @@ -1740,6 +2253,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "SubtractDestAtop" compositing equation. /// @@ -1770,6 +2296,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Subtract(source, backdrop)); } + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + /// /// Returns the result of the "SubtractDestOver" compositing equation. /// @@ -1800,6 +2341,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Subtract(source, backdrop)); } + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Subtract(source, backdrop)); + } + /// /// Returns the result of the "SubtractDestIn" compositing equation. /// @@ -1826,6 +2382,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "SubtractDestOut" compositing equation. /// @@ -1852,6 +2419,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "SubtractXor" compositing equation. /// @@ -1878,6 +2456,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "SubtractClear" compositing equation. /// @@ -1904,6 +2493,17 @@ internal static partial class PorterDuffFunctions public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "SubtractSrc" compositing equation. @@ -2123,6 +2723,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "ScreenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "ScreenSrcAtop" compositing equation. /// @@ -2153,6 +2764,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Screen(backdrop, source)); } + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Screen(backdrop, source)); + } + /// /// Returns the result of the "ScreenSrcOver" compositing equation. /// @@ -2183,6 +2809,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Screen(backdrop, source)); } + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Screen(backdrop, source)); + } + /// /// Returns the result of the "ScreenSrcIn" compositing equation. /// @@ -2209,6 +2850,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "ScreenSrcOut" compositing equation. /// @@ -2235,6 +2887,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "ScreenDest" compositing equation. /// @@ -2261,6 +2924,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "ScreenDestAtop" compositing equation. /// @@ -2282,11 +2958,26 @@ internal static partial class PorterDuffFunctions /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + public static Vector512 ScreenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) { - source = Avx.Blend(source, source * opacity, BlendAlphaControl); + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Screen(source, backdrop)); } @@ -2321,6 +3012,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Screen(source, backdrop)); } + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Screen(source, backdrop)); + } + /// /// Returns the result of the "ScreenDestIn" compositing equation. /// @@ -2347,6 +3053,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "ScreenDestOut" compositing equation. /// @@ -2373,6 +3090,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "ScreenXor" compositing equation. /// @@ -2399,6 +3127,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "ScreenClear" compositing equation. /// @@ -2425,6 +3164,17 @@ internal static partial class PorterDuffFunctions public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "ScreenSrc" compositing equation. @@ -2644,6 +3394,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "DarkenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "DarkenSrcAtop" compositing equation. /// @@ -2674,6 +3435,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Darken(backdrop, source)); } + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Darken(backdrop, source)); + } + /// /// Returns the result of the "DarkenSrcOver" compositing equation. /// @@ -2704,6 +3480,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Darken(backdrop, source)); } + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Darken(backdrop, source)); + } + /// /// Returns the result of the "DarkenSrcIn" compositing equation. /// @@ -2730,6 +3521,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "DarkenSrcOut" compositing equation. /// @@ -2756,6 +3558,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "DarkenDest" compositing equation. /// @@ -2782,6 +3595,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "DarkenDestAtop" compositing equation. /// @@ -2812,6 +3638,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Darken(source, backdrop)); } + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Darken(source, backdrop)); + } + /// /// Returns the result of the "DarkenDestOver" compositing equation. /// @@ -2842,6 +3683,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Darken(source, backdrop)); } + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Darken(source, backdrop)); + } + /// /// Returns the result of the "DarkenDestIn" compositing equation. /// @@ -2868,6 +3724,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "DarkenDestOut" compositing equation. /// @@ -2894,6 +3761,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "DarkenXor" compositing equation. /// @@ -2920,6 +3798,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "DarkenClear" compositing equation. /// @@ -2946,6 +3835,17 @@ internal static partial class PorterDuffFunctions public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "DarkenSrc" compositing equation. @@ -3165,6 +4065,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "LightenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "LightenSrcAtop" compositing equation. /// @@ -3195,6 +4106,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Lighten(backdrop, source)); } + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + /// /// Returns the result of the "LightenSrcOver" compositing equation. /// @@ -3225,6 +4151,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Lighten(backdrop, source)); } + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Lighten(backdrop, source)); + } + /// /// Returns the result of the "LightenSrcIn" compositing equation. /// @@ -3251,6 +4192,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "LightenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "LightenSrcOut" compositing equation. /// @@ -3277,6 +4229,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "LightenDest" compositing equation. /// @@ -3303,6 +4266,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "LightenDestAtop" compositing equation. /// @@ -3333,6 +4309,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Lighten(source, backdrop)); } + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + /// /// Returns the result of the "LightenDestOver" compositing equation. /// @@ -3363,6 +4354,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Lighten(source, backdrop)); } + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Lighten(source, backdrop)); + } + /// /// Returns the result of the "LightenDestIn" compositing equation. /// @@ -3386,8 +4392,19 @@ internal static partial class PorterDuffFunctions /// The source opacity. Range 0..1 /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "LightenDestOut" compositing equation. @@ -3415,6 +4432,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "LightenXor" compositing equation. /// @@ -3441,6 +4469,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "LightenClear" compositing equation. /// @@ -3467,6 +4506,17 @@ internal static partial class PorterDuffFunctions public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "LightenSrc" compositing equation. @@ -3686,6 +4736,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "OverlaySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "OverlaySrcAtop" compositing equation. /// @@ -3716,6 +4777,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, Overlay(backdrop, source)); } + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + /// /// Returns the result of the "OverlaySrcOver" compositing equation. /// @@ -3746,6 +4822,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, Overlay(backdrop, source)); } + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Overlay(backdrop, source)); + } + /// /// Returns the result of the "OverlaySrcIn" compositing equation. /// @@ -3772,6 +4863,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "OverlaySrcOut" compositing equation. /// @@ -3798,6 +4900,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "OverlayDest" compositing equation. /// @@ -3824,6 +4937,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "OverlayDestAtop" compositing equation. /// @@ -3854,6 +4980,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, Overlay(source, backdrop)); } + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + /// /// Returns the result of the "OverlayDestOver" compositing equation. /// @@ -3884,6 +5025,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, Overlay(source, backdrop)); } + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Overlay(source, backdrop)); + } + /// /// Returns the result of the "OverlayDestIn" compositing equation. /// @@ -3910,6 +5066,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "OverlayDestOut" compositing equation. /// @@ -3936,6 +5103,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "OverlayXor" compositing equation. /// @@ -3962,6 +5140,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "OverlayClear" compositing equation. /// @@ -3988,6 +5177,17 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "OverlaySrc" compositing equation. @@ -4207,6 +5407,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "HardLightSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "HardLightSrcAtop" compositing equation. /// @@ -4237,6 +5448,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, HardLight(backdrop, source)); } + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + /// /// Returns the result of the "HardLightSrcOver" compositing equation. /// @@ -4267,6 +5493,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, HardLight(backdrop, source)); } + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, HardLight(backdrop, source)); + } + /// /// Returns the result of the "HardLightSrcIn" compositing equation. /// @@ -4293,6 +5534,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "HardLightSrcOut" compositing equation. /// @@ -4319,6 +5571,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "HardLightDest" compositing equation. /// @@ -4345,6 +5608,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "HardLightDestAtop" compositing equation. /// @@ -4375,6 +5651,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, HardLight(source, backdrop)); } + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + /// /// Returns the result of the "HardLightDestOver" compositing equation. /// @@ -4405,6 +5696,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, HardLight(source, backdrop)); } + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, HardLight(source, backdrop)); + } + /// /// Returns the result of the "HardLightDestIn" compositing equation. /// @@ -4431,6 +5737,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "HardLightDestOut" compositing equation. /// @@ -4457,6 +5774,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "HardLightXor" compositing equation. /// @@ -4483,6 +5811,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "HardLightClear" compositing equation. /// @@ -4509,6 +5848,17 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "HardLightSrc" compositing equation. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 83bc055ef..7cb007bca 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -49,6 +49,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>Src(Vector256 backdrop, Vector256 source, Vector256 opacity) => Avx.Blend(source, source * opacity, BlendAlphaControl); + /// + /// Returns the result of the "<#=blender#>Src compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Src(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + /// /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. /// @@ -79,6 +90,21 @@ internal static partial class PorterDuffFunctions return Atop(backdrop, source, <#=blender#>(backdrop, source)); } + /// + /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, <#=blender#>(backdrop, source)); + } + /// /// Returns the result of the "<#=blender#>SrcOver" compositing equation. /// @@ -109,6 +135,21 @@ internal static partial class PorterDuffFunctions return Over(backdrop, source, <#=blender#>(backdrop, source)); } + /// + /// Returns the result of the "<#=blender#>SrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, <#=blender#>(backdrop, source)); + } + /// /// Returns the result of the "<#=blender#>SrcIn" compositing equation. /// @@ -135,6 +176,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>SrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "<#=blender#>SrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "<#=blender#>SrcOut" compositing equation. /// @@ -161,6 +213,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>SrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "<#=blender#>SrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "<#=blender#>Dest" compositing equation. /// @@ -187,6 +250,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "<#=blender#>Dest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Dest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "<#=blender#>DestAtop" compositing equation. /// @@ -217,6 +293,21 @@ internal static partial class PorterDuffFunctions return Atop(source, backdrop, <#=blender#>(source, backdrop)); } + /// + /// Returns the result of the "<#=blender#>DestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, <#=blender#>(source, backdrop)); + } + /// /// Returns the result of the "<#=blender#>DestOver" compositing equation. /// @@ -247,6 +338,21 @@ internal static partial class PorterDuffFunctions return Over(source, backdrop, <#=blender#>(source, backdrop)); } + /// + /// Returns the result of the "<#=blender#>DestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, <#=blender#>(source, backdrop)); + } + /// /// Returns the result of the "<#=blender#>DestIn" compositing equation. /// @@ -273,6 +379,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>DestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "<#=blender#>DestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "<#=blender#>DestOut" compositing equation. /// @@ -299,6 +416,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>DestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + /// + /// Returns the result of the "<#=blender#>DestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + /// /// Returns the result of the "<#=blender#>Xor" compositing equation. /// @@ -325,6 +453,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>Xor(Vector256 backdrop, Vector256 source, Vector256 opacity) => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "<#=blender#>Xor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Xor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + /// /// Returns the result of the "<#=blender#>Clear" compositing equation. /// @@ -351,6 +490,17 @@ internal static partial class PorterDuffFunctions public static Vector256 <#=blender#>Clear(Vector256 backdrop, Vector256 source, Vector256 opacity) => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + /// + /// Returns the result of the "<#=blender#>Clear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Clear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + <#} #> <# void GenerateGenericPixelBlender(string blender, string composer) { #> diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 45c4aade7..948076fa3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -45,6 +45,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Normal(Vector256 backdrop, Vector256 source) => source; + /// + /// Returns the result of the "Normal" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Normal(Vector512 backdrop, Vector512 source) + => source; + /// /// Returns the result of the "Multiply" compositing equation. /// @@ -65,6 +75,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Multiply(Vector256 backdrop, Vector256 source) => backdrop * source; + /// + /// Returns the result of the "Multiply" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Multiply(Vector512 backdrop, Vector512 source) + => backdrop * source; + /// /// Returns the result of the "Add" compositing equation. /// @@ -85,6 +105,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Add(Vector256 backdrop, Vector256 source) => Vector256.Min(Vector256.Create(1F), backdrop + source); + /// + /// Returns the result of the "Add" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Add(Vector512 backdrop, Vector512 source) + => Vector512.Min(Vector512.Create(1F), backdrop + source); + /// /// Returns the result of the "Subtract" compositing equation. /// @@ -105,6 +135,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Subtract(Vector256 backdrop, Vector256 source) => Vector256.Max(Vector256.Zero, backdrop - source); + /// + /// Returns the result of the "Subtract" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Subtract(Vector512 backdrop, Vector512 source) + => Vector512.Max(Vector512.Zero, backdrop - source); + /// /// Returns the result of the "Screen" compositing equation. /// @@ -128,6 +168,19 @@ internal static partial class PorterDuffFunctions return Vector256_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source); } + /// + /// Returns the result of the "Screen" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Screen(Vector512 backdrop, Vector512 source) + { + Vector512 vOne = Vector512.Create(1F); + return Vector512_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source); + } + /// /// Returns the result of the "Darken" compositing equation. /// @@ -148,6 +201,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Darken(Vector256 backdrop, Vector256 source) => Vector256.Min(backdrop, source); + /// + /// Returns the result of the "Darken" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Darken(Vector512 backdrop, Vector512 source) + => Vector512.Min(backdrop, source); + /// /// Returns the result of the "Lighten" compositing equation. /// @@ -167,6 +230,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Lighten(Vector256 backdrop, Vector256 source) => Vector256.Max(backdrop, source); + /// + /// Returns the result of the "Lighten" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Lighten(Vector512 backdrop, Vector512 source) + => Vector512.Max(backdrop, source); + /// /// Returns the result of the "Overlay" compositing equation. /// @@ -196,6 +269,19 @@ internal static partial class PorterDuffFunctions return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); } + /// + /// Returns the result of the "Overlay" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Overlay(Vector512 backdrop, Vector512 source) + { + Vector512 color = OverlayValueFunction(backdrop, source); + return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, color)); + } + /// /// Returns the result of the "HardLight" compositing equation. /// @@ -225,6 +311,19 @@ internal static partial class PorterDuffFunctions return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); } + /// + /// Returns the result of the "HardLight" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLight(Vector512 backdrop, Vector512 source) + { + Vector512 color = OverlayValueFunction(source, backdrop); + return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, color)); + } + /// /// Helper function for Overlay and HardLight modes /// @@ -253,6 +352,24 @@ internal static partial class PorterDuffFunctions return Avx.BlendVariable(left, right, cmp); } + /// + /// Helper function for Overlay and HardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayValueFunction(Vector512 backdrop, Vector512 source) + { + Vector512 vOne = Vector512.Create(1F); + Vector512 left = (backdrop + backdrop) * source; + + Vector512 vOneMinusSource = vOne - source; + Vector512 right = Vector512_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop); + Vector512 cmp = Avx512F.CompareGreaterThan(backdrop, Vector512.Create(.5F)); + return Vector512.ConditionalSelect(cmp, right, left); + } + /// /// Returns the result of the "Over" compositing equation. /// @@ -312,6 +429,36 @@ internal static partial class PorterDuffFunctions return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "Over" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Over(Vector512 destination, Vector512 source, Vector512 blend) + { + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + Vector512 blendW = sW * dW; + Vector512 dstW = dW - blendW; + Vector512 srcW = sW - blendW; + + // calculate final alpha + Vector512 alpha = dstW + sW; + + // calculate final color + Vector512 color = destination * dstW; + color = Vector512_.MultiplyAdd(color, source, srcW); + color = Vector512_.MultiplyAdd(color, blend, blendW); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + /// /// Returns the result of the "Atop" compositing equation. /// @@ -365,6 +512,31 @@ internal static partial class PorterDuffFunctions return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "Atop" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Atop(Vector512 destination, Vector512 source, Vector512 blend) + { + // calculate final alpha + Vector512 alpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 blendW = sW * alpha; + Vector512 dstW = alpha - blendW; + + // calculate final color + Vector512 color = Vector512_.MultiplyAdd(blend * blendW, destination, dstW); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + /// /// Returns the result of the "In" compositing equation. /// @@ -402,6 +574,25 @@ internal static partial class PorterDuffFunctions return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "In" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 In(Vector512 destination, Vector512 source) + { + // calculate alpha + Vector512 alpha = Vector512_.ShuffleNative(source * destination, ShuffleAlphaControl); + + // premultiply + Vector512 color = source * alpha; + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + /// /// Returns the result of the "Out" compositing equation. /// @@ -439,6 +630,25 @@ internal static partial class PorterDuffFunctions return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "Out" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Out(Vector512 destination, Vector512 source) + { + // calculate alpha + Vector512 alpha = Vector512_.ShuffleNative(source * (Vector512.Create(1F) - destination), ShuffleAlphaControl); + + // premultiply + Vector512 color = source * alpha; + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + /// /// Returns the result of the "XOr" compositing equation. /// @@ -487,9 +697,41 @@ internal static partial class PorterDuffFunctions return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "XOr" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Xor(Vector512 destination, Vector512 source) + { + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + Vector512 vOne = Vector512.Create(1F); + Vector512 srcW = vOne - dW; + Vector512 dstW = vOne - sW; + + // calculate alpha + Vector512 alpha = Vector512_.MultiplyAdd(dW * dstW, sW, srcW); + Vector512 color = Vector512_.MultiplyAdd((dW * destination) * dstW, sW * source, srcW); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 Clear(Vector4 backdrop, Vector4 source) => Vector4.Zero; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector256 Clear(Vector256 backdrop, Vector256 source) => Vector256.Zero; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Clear(Vector512 backdrop, Vector512 source) => Vector512.Zero; + + [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/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs index ecf8b125f..fe43ce5e7 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs @@ -18,8 +18,8 @@ public class PorterDuffBulkVsSingleVector [GlobalSetup] public void Setup() { - this.backdrop = new Vector4[8 * 20]; - this.source = new Vector4[8 * 20]; + this.backdrop = new Vector4[8 * 40]; + this.source = new Vector4[8 * 40]; FillRandom(this.backdrop); FillRandom(this.source); @@ -49,7 +49,7 @@ public class PorterDuffBulkVsSingleVector return result; } - [Benchmark(Description = "Avx")] + [Benchmark(Description = "Avx2")] public Vector256 OverlayValueFunction_Avx() { ref Vector256 backdrop = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.backdrop)); @@ -65,4 +65,21 @@ public class PorterDuffBulkVsSingleVector return result; } + + [Benchmark(Description = "Avx512")] + public Vector512 OverlayValueFunction_Avx512() + { + ref Vector512 backdrop = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.backdrop)); + ref Vector512 source = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.source)); + + Vector512 result = default; + Vector512 opacity = Vector512.Create(.5F); + int count = this.backdrop.Length / 4; + for (nuint i = 0; i < (uint)count; i++) + { + result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity); + } + + return result; + } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 1086afe76..994b7d02e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -59,7 +59,7 @@ public class PorterDuffCompositorTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX, provider, mode.ToString()); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 976a272eb..0def09788 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -4,7 +4,6 @@ using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using Castle.Components.DictionaryAdapter; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -45,6 +44,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.NormalSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData MultiplyFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -77,6 +92,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.MultiplySrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData AddFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -109,6 +140,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.AddSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData SubtractFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, @@ -141,6 +188,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.SubtractSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData ScreenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -173,6 +236,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.ScreenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData DarkenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -205,6 +284,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.DarkenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData LightenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -237,6 +332,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.LightenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData OverlayFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -269,6 +380,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.OverlaySrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData HardLightFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -300,4 +427,27 @@ public class PorterDuffFunctionsTests Vector256 actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount)); Assert.Equal(expected256, actual, FloatComparer); } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.HardLightSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + + private static Vector512 CreateVector512(TestVector4 vector) + => Vector512.Create( + vector.X, vector.Y, vector.Z, vector.W, + vector.X, vector.Y, vector.Z, vector.W, + vector.X, vector.Y, vector.Z, vector.W, + vector.X, vector.Y, vector.Z, vector.W); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs index 2c97cbde0..153a9ac48 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -9,12 +9,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; public class PorterDuffFunctionsTestsTPixel { + private const int BulkBlendCount = 4; + private static Span AsSpan(T value) where T : struct { return new Span(new[] { value }); } + private static T[] CreateFilledArray(T value) + { + T[] values = new T[BulkBlendCount]; + values.AsSpan().Fill(value); + return values; + } + public static TheoryData NormalBlendFunctionData = new() { { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, @@ -46,9 +55,14 @@ public class PorterDuffFunctionsTestsTPixel public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData MultiplyFunctionData = new() @@ -86,9 +100,14 @@ public class PorterDuffFunctionsTestsTPixel public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData AddFunctionData = new() @@ -136,9 +155,14 @@ public class PorterDuffFunctionsTestsTPixel public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData SubtractFunctionData = new() @@ -176,9 +200,14 @@ public class PorterDuffFunctionsTestsTPixel public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData ScreenFunctionData = new() @@ -216,9 +245,14 @@ public class PorterDuffFunctionsTestsTPixel public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData DarkenFunctionData = new() @@ -256,9 +290,14 @@ public class PorterDuffFunctionsTestsTPixel public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData LightenFunctionData = new() @@ -296,9 +335,14 @@ public class PorterDuffFunctionsTestsTPixel public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData OverlayFunctionData = new() @@ -336,9 +380,14 @@ public class PorterDuffFunctionsTestsTPixel public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData HardLightFunctionData = new() @@ -376,8 +425,13 @@ public class PorterDuffFunctionsTestsTPixel public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 21ac6966b..7c45dd047 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -15,7 +15,8 @@ internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer> + IEqualityComparer>, + IEqualityComparer> { private readonly float epsilon; @@ -78,4 +79,19 @@ internal readonly struct ApproximateFloatComparer : && this.Equals(x.GetElement(7), y.GetElement(7)); public int GetHashCode([DisallowNull] Vector256 obj) => obj.GetHashCode(); + + public bool Equals(Vector512 x, Vector512 y) + { + for (int i = 0; i < Vector512.Count; i++) + { + if (!this.Equals(x.GetElement(i), y.GetElement(i))) + { + return false; + } + } + + return true; + } + + public int GetHashCode([DisallowNull] Vector512 obj) => obj.GetHashCode(); } From e611b1e018e194dec99413f5ca30b2fd4b4dcbac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2026 19:07:38 +0200 Subject: [PATCH 125/240] Add tests for Rgba128 --- .../PixelFormats/Rgba128Tests.cs | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs new file mode 100644 index 000000000..f00367187 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs @@ -0,0 +1,313 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgba128Tests +{ + [Theory] + [InlineData(1, 2, 3, 4)] + [InlineData(50, 70, 80, 90)] + [InlineData(1000, 2000, 3000, 4000)] + public void Constructor(uint r, uint g, uint b, uint a) + { + Rgba128 p = new(r, g, b, a); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public void Rgba128_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba128(0, 0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgba128(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); + Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 0.5F), new Rgba128(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); + } + + [Theory] + [InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue)] + [InlineData(0, 0, 0, 0)] + [InlineData(uint.MaxValue / 2, 100, 2222, 3333)] + public void Rgba128_ToScaledVector4(uint r, uint g, uint b, uint a) + { + // arrange + Rgba128 rgb = new(r, g, b, a); + + float max = uint.MaxValue; + float rr = r / max; + float gg = g / max; + float bb = b / max; + float aa = a / max; + + // act + Vector4 actual = rgb.ToScaledVector4(); + + // assert + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(aa, actual.W); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(5000, 100, 2222, 3333)] + public void Rgba128_FromScaledVector4(uint r, uint g, uint b, uint a) + { + // arrange + Rgba128 source = new(r, g, b, a); + Vector4 scaled = source.ToScaledVector4(); + + // act + Rgba128 actual = Rgba128.FromScaledVector4(scaled); + + // assert + Assert.Equal(source, actual); + } + + [Fact] + public void Rgba128_ToRgba32() + { + // arrange + Rgba128 rgb96 = new((uint)(uint.MaxValue * 0.1f), uint.MaxValue / 2, uint.MaxValue, uint.MaxValue); + Rgba32 expected = new(25, 127, 255, 255); + + // act + Rgba32 actual = rgb96.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Equality_WhenTrue() + { + Rgba128 c1 = new(100, 2000, 3000, 4000); + Rgba128 c2 = new(100, 2000, 3000, 4000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Rgba128 c1 = new(100, 2000, 3000, 4000); + Rgba128 c2 = new(101, 2000, 3000, 4000); + Rgba128 c3 = new(100, 2000, 3001, 4000); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgba128_FromRgba32() + { + // arrange + Rgba32 source = new(25, 127, 255, 255); + Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgba32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgb24() + { + // arrange + Rgb24 source = new(25, 127, 255); + Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgb24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgb48() + { + // arrange + Rgb48 source = new(0, 32767, ushort.MaxValue); + Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgb48(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgba64() + { + // arrange + Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); + Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgba64(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromLa32() + { + // arrange + La32 source = new(32767, ushort.MaxValue); + Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); + + // act + Rgba128 actual = Rgba128.FromLa32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromLa16() + { + // arrange + La16 source = new(127, byte.MaxValue); + Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); + + // act + Rgba128 actual = Rgba128.FromLa16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromL16() + { + // arrange + L16 source = new(32767); + Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); + + // act + Rgba128 actual = Rgba128.FromL16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromL8() + { + // arrange + L8 source = new(127); + Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); + + // act + Rgba128 actual = Rgba128.FromL8(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgra32() + { + // arrange + Bgra32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgra32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgr24() + { + // arrange + Bgr24 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgr24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgra5551() + { + // arrange + Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); + Rgba128 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgra5551(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromArgb32() + { + // arrange + Argb32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromArgb32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromAbgr32() + { + // arrange + Abgr32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromAbgr32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_PixelInformation() + { + PixelTypeInfo info = Rgba128.GetPixelTypeInfo(); + Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); + Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); + + PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; + Assert.NotNull(maybeComponentInfo); + PixelComponentInfo componentInfo = maybeComponentInfo.Value; + + Assert.Equal(4, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + Assert.Equal(32, componentInfo.GetComponentPrecision(0)); + Assert.Equal(32, componentInfo.GetComponentPrecision(1)); + Assert.Equal(32, componentInfo.GetComponentPrecision(2)); + Assert.Equal(32, componentInfo.GetComponentPrecision(3)); + Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); + } +} From 24dbf639c55d062f9f030b4b061ae370e9df8419 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Apr 2026 19:08:11 +0200 Subject: [PATCH 126/240] Fix issues with pixel conversions in Rgba128 --- .../PixelImplementations/Rgba128.cs | 60 +++++++++++++------ .../PixelImplementations/Rgba32.cs | 15 +++++ 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index c748456b8..77934eac8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -19,7 +19,7 @@ public partial struct Rgba128 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; - private const double Max = uint.MaxValue; + private const float Max = uint.MaxValue; /// /// Gets the red component. @@ -94,62 +94,86 @@ public partial struct Rgba128 : IPixel, IEquatable public static Rgba128 FromScaledVector4(Vector4 source) => FromVector4(source); /// - public static Rgba128 FromVector4(Vector4 source) => FromVector4(source); + public static Rgba128 FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One) * Max; + return new Rgba128((uint)MathF.Round(source.X), (uint)MathF.Round(source.Y), (uint)MathF.Round(source.Z), (uint)MathF.Round(source.W)); + } /// - public static Rgba128 FromAbgr32(Abgr32 source) => new(source.R, source.G, source.B, source.A); + public static Rgba128 FromAbgr32(Abgr32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); /// - public static Rgba128 FromArgb32(Argb32 source) => new(source.R, source.G, source.B, source.A); + public static Rgba128 FromArgb32(Argb32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); /// public static Rgba128 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); /// - public static Rgba128 FromBgr24(Bgr24 source) => new(source.R, source.G, source.B, uint.MaxValue); + public static Rgba128 FromBgr24(Bgr24 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); /// - public static Rgba128 FromBgra32(Bgra32 source) => new(source.R, source.G, source.B, source.A); + public static Rgba128 FromBgra32(Bgra32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); /// public static Rgba128 FromL8(L8 source) { - ushort rgb = ColorNumerics.From8BitTo16Bit(source.PackedValue); + uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); return new Rgba128(rgb, rgb, rgb, rgb); } /// - public static Rgba128 FromL16(L16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue, source.PackedValue); + public static Rgba128 FromL16(L16 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); + return new(rgb, rgb, rgb, rgb); + } /// - public static Rgba128 FromLa16(La16 source) => new(source.PackedValue, source.PackedValue, source.PackedValue, source.PackedValue); + public static Rgba128 FromLa16(La16 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); + return new(rgb, rgb, rgb, rgb); + } /// - public static Rgba128 FromLa32(La32 source) => new(source.L, source.L, source.L, source.L); + public static Rgba128 FromLa32(La32 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.L); + return new(rgb, rgb, rgb, rgb); + } /// - public static Rgba128 FromRgb24(Rgb24 source) => new(source.R, source.G, source.B, uint.MaxValue); + public static Rgba128 FromRgb24(Rgb24 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); /// - public static Rgba128 FromRgba32(Rgba32 source) => new(source.R, source.G, source.B, source.A); + public static Rgba128 FromRgba32(Rgba32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); /// - public static Rgba128 FromRgb48(Rgb48 source) => new(source.R, source.G, source.B, uint.MaxValue); + public static Rgba128 FromRgb48(Rgb48 source) + => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), uint.MaxValue); /// - public static Rgba128 FromRgba64(Rgba64 source) => new(source.R, source.G, source.B, source.A); + public static Rgba128 FromRgba64(Rgba64 source) + => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), ColorNumerics.From16BitTo32Bit(source.A)); /// public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( - PixelComponentInfo.Create(4, 32, 32, 32), - PixelColorType.RGB, + PixelComponentInfo.Create(4, 32, 32, 32, 32), + PixelColorType.RGB | PixelColorType.Alpha, PixelAlphaRepresentation.Unassociated); /// - public readonly Rgba32 ToRgba32() => throw new NotImplementedException(); + public readonly Rgba32 ToRgba32() => Rgba32.FromRgba128(this); /// - public readonly Vector4 ToScaledVector4() => throw new NotImplementedException(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 72215dec0..1eafab854 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -329,6 +329,21 @@ public partial struct Rgba32 : IPixel, IPackedVector A = byte.MaxValue }; + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + /// The pixel value as Rgba32. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromRgba128(Rgba128 source) + => new() + { + R = ColorNumerics.From32BitTo8Bit(source.R), + G = ColorNumerics.From32BitTo8Bit(source.G), + B = ColorNumerics.From32BitTo8Bit(source.B), + A = ColorNumerics.From32BitTo8Bit(source.A), + }; + /// /// Converts the value of this instance to a hexadecimal string. /// From ad58e74505414da37e3f961ab1b03e79ac983abb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 9 Apr 2026 11:58:22 +1000 Subject: [PATCH 127/240] Allow -1 (unbounded) parallelism; validate settings --- .../Advanced/ParallelExecutionSettings.cs | 11 +- .../Advanced/ParallelRowIterator.cs | 86 +++++++++++-- src/ImageSharp/Configuration.cs | 1 + .../Helpers/ParallelRowIteratorTests.cs | 115 ++++++++++++++++++ 4 files changed, 199 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs index fd9692f9a..ad0318297 100644 --- a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -18,7 +18,10 @@ public readonly struct ParallelExecutionSettings /// /// Initializes a new instance of the struct. /// - /// The value used for initializing when using TPL. + /// + /// The value used for initializing when using TPL. + /// Set to -1 to leave the degree of parallelism unbounded. + /// /// The value for . /// The . public ParallelExecutionSettings( @@ -44,7 +47,10 @@ public readonly struct ParallelExecutionSettings /// /// Initializes a new instance of the struct. /// - /// The value used for initializing when using TPL. + /// + /// The value used for initializing when using TPL. + /// Set to -1 to leave the degree of parallelism unbounded. + /// /// The . public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) @@ -58,6 +64,7 @@ public readonly struct ParallelExecutionSettings /// /// Gets the value used for initializing when using TPL. + /// A value of -1 leaves the degree of parallelism unbounded. /// public int MaxDegreeOfParallelism { get; } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index d170631a2..98c2656d1 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -44,14 +44,14 @@ public static partial class ParallelRowIterator where T : struct, IRowOperation { ValidateRectangle(rectangle); + ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; int width = rectangle.Width; int height = rectangle.Height; - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) @@ -65,7 +65,7 @@ public static partial class ParallelRowIterator } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; + ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); _ = Parallel.For( @@ -109,14 +109,14 @@ public static partial class ParallelRowIterator where TBuffer : unmanaged { ValidateRectangle(rectangle); + ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; int width = rectangle.Width; int height = rectangle.Height; - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); MemoryAllocator allocator = parallelSettings.MemoryAllocator; int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); @@ -135,7 +135,7 @@ public static partial class ParallelRowIterator } int verticalStep = DivideCeil(height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; + ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); _ = Parallel.For( @@ -174,14 +174,14 @@ public static partial class ParallelRowIterator where T : struct, IRowIntervalOperation { ValidateRectangle(rectangle); + ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; int width = rectangle.Width; int height = rectangle.Height; - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) @@ -192,7 +192,7 @@ public static partial class ParallelRowIterator } int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; + ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation); _ = Parallel.For( @@ -236,14 +236,14 @@ public static partial class ParallelRowIterator where TBuffer : unmanaged { ValidateRectangle(rectangle); + ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; int width = rectangle.Width; int height = rectangle.Height; - int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + int numOfSteps = GetNumberOfSteps(width, height, parallelSettings); MemoryAllocator allocator = parallelSettings.MemoryAllocator; int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle); @@ -259,7 +259,7 @@ public static partial class ParallelRowIterator } int verticalStep = DivideCeil(height, numOfSteps); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps }; + ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps); RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation); _ = Parallel.For( @@ -272,6 +272,37 @@ public static partial class ParallelRowIterator [MethodImpl(InliningOptions.ShortMethod)] private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue); + /// + /// Creates the for the current iteration. + /// + /// The execution settings. + /// The number of row partitions to execute. + /// The instance. + [MethodImpl(InliningOptions.ShortMethod)] + private static ParallelOptions CreateParallelOptions(in ParallelExecutionSettings parallelSettings, int numOfSteps) + => new() { MaxDegreeOfParallelism = parallelSettings.MaxDegreeOfParallelism == -1 ? -1 : numOfSteps }; + + /// + /// Calculates the number of row partitions to execute for the given region. + /// + /// The width of the region. + /// The height of the region. + /// The execution settings. + /// The number of row partitions to execute. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetNumberOfSteps(int width, int height, in ParallelExecutionSettings parallelSettings) + { + int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask); + + if (parallelSettings.MaxDegreeOfParallelism == -1) + { + // Row batching cannot produce more useful partitions than the number of rows available. + return Math.Min(height, maxSteps); + } + + return Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + } + private static void ValidateRectangle(Rectangle rectangle) { Guard.MustBeGreaterThan( @@ -284,4 +315,35 @@ public static partial class ParallelRowIterator 0, $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); } + + /// + /// Validates the supplied . + /// + /// The execution settings. + /// + /// Thrown when or + /// is invalid. + /// + /// + /// Thrown when is null. + /// This also guards the public default value, which bypasses constructor validation. + /// + private static void ValidateSettings(in ParallelExecutionSettings parallelSettings) + { + // ParallelExecutionSettings is a public struct, so callers can pass default and bypass constructor validation. + if (parallelSettings.MaxDegreeOfParallelism is 0 or < -1) + { + throw new ArgumentOutOfRangeException( + $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MaxDegreeOfParallelism)}"); + } + + Guard.MustBeGreaterThan( + parallelSettings.MinimumPixelsProcessedPerTask, + 0, + $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MinimumPixelsProcessedPerTask)}"); + + Guard.NotNull( + parallelSettings.MemoryAllocator, + $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MemoryAllocator)}"); + } } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c2b02dedd..267392723 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -64,6 +64,7 @@ public sealed class Configuration /// /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms /// configured with this instance. + /// Set to -1 to leave the degree of parallelism unbounded. /// Initialized with by default. /// public int MaxDegreeOfParallelism diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 4b06f877f..cf68f702a 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers; public class ParallelRowIteratorTests { + public delegate void BufferedRowAction(int y, Span span); public delegate void RowIntervalAction(RowInterval rows, Span span); private readonly ITestOutputHelper output; @@ -200,6 +201,47 @@ public class ParallelRowIteratorTests Assert.Equal(expectedData, actualData); } + [Fact] + public void IterateRows_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows() + { + ParallelExecutionSettings parallelSettings = new( + -1, + 10, + Configuration.Default.MemoryAllocator); + + Rectangle rectangle = new(0, 0, 10, 10); + int[] actualData = new int[rectangle.Height]; + + void RowAction(int y) => actualData[y]++; + + TestRowActionOperation operation = new(RowAction); + + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); + } + + [Fact] + public void IterateRowsWithTempBuffer_DefaultSettingsRequireInitialization() + { + ParallelExecutionSettings parallelSettings = default; + Rectangle rect = new(0, 0, 10, 10); + + void RowAction(int y, Span memory) + { + } + + TestRowOperation operation = new(RowAction); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRows, Rgba32>(rect, in parallelSettings, in operation)); + + Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message); + } + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new() { @@ -296,6 +338,53 @@ public class ParallelRowIteratorTests Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } + [Fact] + public void IterateRowIntervalsWithTempBuffer_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows() + { + ParallelExecutionSettings parallelSettings = new( + -1, + 10, + Configuration.Default.MemoryAllocator); + + Rectangle rectangle = new(0, 0, 10, 10); + int[] actualData = new int[rectangle.Height]; + + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y]++; + } + } + + TestRowIntervalOperation operation = new(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); + } + + [Fact] + public void IterateRows_DefaultSettingsRequireInitialization() + { + ParallelExecutionSettings parallelSettings = default; + Rectangle rect = new(0, 0, 10, 10); + + void RowAction(int y) + { + } + + TestRowActionOperation operation = new(RowAction); + + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); + + Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message); + } + public static readonly TheoryData IterateRectangularBuffer_Data = new() { @@ -445,6 +534,32 @@ public class ParallelRowIteratorTests } } + private readonly struct TestRowActionOperation : IRowOperation + { + private readonly Action action; + + public TestRowActionOperation(Action action) + => this.action = action; + + public void Invoke(int y) + => this.action(y); + } + + private readonly struct TestRowOperation : IRowOperation + where TBuffer : unmanaged + { + private readonly BufferedRowAction action; + + public TestRowOperation(BufferedRowAction action) + => this.action = action; + + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + + public void Invoke(int y, Span span) + => this.action(y, span); + } + private readonly struct TestRowIntervalOperation : IRowIntervalOperation { private readonly Action action; From 25b3567482eb434f8bd5daf8c0aaf84c22c2bb49 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 10 Apr 2026 03:55:58 +0200 Subject: [PATCH 128/240] Benchmark parallelization (#3111) * add parallel processing benchmarks * introduce RunExperiment and fix warnings in test code * fix benchmarks * proper ProcessorThroughputTest * test additional processors * ProcessorThroughputTest -> ProcessorThroughputBenchmark * add DrawImage to ParallelProcessing benchmark * readonly * Disable BMP tests -- https://github.com/SixLabors/ImageSharp/issues/3112 * revert ImageMagick update * delete CountingUnit * rename field --- .../LoadResizeSaveStressRunner.cs | 12 +- .../Processing/ParallelProcessing.cs | 65 +++++ .../ImageSharp.Tests.ProfilingSandbox.csproj | 1 - .../LoadResizeSaveParallelMemoryStress.cs | 13 +- .../ProcessorThroughputBenchmark.cs | 243 ++++++++++++++++++ .../Program.cs | 80 ++---- 6 files changed, 344 insertions(+), 70 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs create mode 100644 tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index f8bf19d57..ad6c8dbd8 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -6,6 +6,7 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats; @@ -27,6 +28,7 @@ public enum JpegKind Any = Baseline | Progressive } +[SupportedOSPlatform("windows")] public class LoadResizeSaveStressRunner { private const int Quality = 75; @@ -158,7 +160,7 @@ public class LoadResizeSaveStressRunner this.outputDirectory, Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); - private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) + private static (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) { int width, height; if (inWidth > inHeight) @@ -180,7 +182,7 @@ public class LoadResizeSaveStressRunner using SystemDrawingImage image = SystemDrawingImage.FromFile(input, true); this.LogImageProcessed(image.Width, image.Height); - (int width, int height) = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(image.Width, image.Height, this.ThumbnailSize); Bitmap resized = new(width, height); using Graphics graphics = Graphics.FromImage(resized); using ImageAttributes attributes = new(); @@ -282,7 +284,7 @@ public class LoadResizeSaveStressRunner { using SKBitmap original = SKBitmap.Decode(input); this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize); using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, original.ColorType, original.AlphaType)); using SKPaint paint = new() { FilterQuality = SKFilterQuality.High }; SKCanvas canvas = surface.Canvas; @@ -300,7 +302,7 @@ public class LoadResizeSaveStressRunner { using SKBitmap original = SKBitmap.Decode(input); this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize); using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High); if (resized == null) { @@ -319,7 +321,7 @@ public class LoadResizeSaveStressRunner SKImageInfo info = codec.Info; this.LogImageProcessed(info.Width, info.Height); - (int width, int height) = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(info.Width, info.Height, this.ThumbnailSize); SKSizeI supportedScale = codec.GetScaledDimensions((float)width / info.Width); using SKBitmap original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height)); diff --git a/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs b/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs new file mode 100644 index 000000000..4f2089713 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks; + +public class ParallelProcessing +{ + private Image image; + private Image foreground; + private Configuration configuration; + + public static IEnumerable MaxDegreeOfParallelismValues() + { + int processorCount = Environment.ProcessorCount; + for (int p = 1; p <= processorCount; p *= 2) + { + yield return p; + } + + if ((processorCount & (processorCount - 1)) != 0) + { + yield return processorCount; + } + } + + [ParamsSource(nameof(MaxDegreeOfParallelismValues))] + public int MaxDegreeOfParallelism { get; set; } + + [GlobalSetup] + public void Setup() + { + this.image = new Image(2048, 2048); + this.foreground = new Image(2048, 2048); + this.configuration = Configuration.Default.Clone(); + this.configuration.MaxDegreeOfParallelism = this.MaxDegreeOfParallelism; + } + + [Benchmark] + public void DetectEdges() => this.image.Mutate(this.configuration, x => x.DetectEdges()); + + [Benchmark] + public void DrawImage() => this.image.Mutate(this.configuration, x => x.DrawImage(this.foreground, 0.5f)); + + [Benchmark] + public void Crop() + { + Rectangle bounds = this.image.Bounds; + bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); + this.image + .Clone(this.configuration, x => x.Crop(bounds)) + .Dispose(); + } + + [GlobalCleanup] + public void Cleanup() + { + this.image.Dispose(); + this.foreground.Dispose(); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index bc52610d2..f3aa910b9 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -8,7 +8,6 @@ false SixLabors.ImageSharp.Tests.ProfilingSandbox win-x64 - SixLabors.ImageSharp.Tests.ProfilingSandbox.Program false false diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 6850756df..5e21b7cc1 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.Runtime.Versioning; using System.Text; using CommandLine; using CommandLine.Text; @@ -13,7 +14,8 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; // See ImageSharp.Benchmarks/LoadResizeSave/README.md -internal class LoadResizeSaveParallelMemoryStress +[SupportedOSPlatform("windows")] +internal sealed class LoadResizeSaveParallelMemoryStress { private LoadResizeSaveParallelMemoryStress() { @@ -206,14 +208,15 @@ internal class LoadResizeSaveParallelMemoryStress StringBuilder bld = new(); bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); bld.AppendLine( + CultureInfo.InvariantCulture, $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); bld.Append("| "); - bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.TotalSeconds)), this.TotalSeconds); bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); bld.AppendLine(" |"); return bld.ToString(); @@ -223,7 +226,7 @@ internal class LoadResizeSaveParallelMemoryStress } } - private class CommandLineOptions + private sealed class CommandLineOptions { [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] public bool AsyncImageSharp { get; set; } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs new file mode 100644 index 000000000..e9adf5844 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs @@ -0,0 +1,243 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using CommandLine; +using CommandLine.Text; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; + +public sealed class ProcessorThroughputBenchmark +{ + private readonly CommandLineOptions options; + private readonly Configuration configuration; + private ulong totalProcessedPixels; + + private ProcessorThroughputBenchmark(CommandLineOptions options) + { + this.options = options; + this.configuration = Configuration.Default.Clone(); + this.configuration.MaxDegreeOfParallelism = options.ProcessorParallelism > 0 + ? options.ProcessorParallelism + : Environment.ProcessorCount; + } + + public static Task RunAsync(string[] args) + { + CommandLineOptions options = null; + if (args.Length > 0) + { + options = CommandLineOptions.Parse(args); + if (options == null) + { + return Task.CompletedTask; + } + } + + options ??= new CommandLineOptions(); + return new ProcessorThroughputBenchmark(options.Normalize()) + .RunAsync(); + } + + private async Task RunAsync() + { + SemaphoreSlim semaphore = new(this.options.ConcurrentRequests); + Console.WriteLine(this.options.Method); + Func action = this.options.Method switch + { + Method.Crop => this.Crop, + Method.Edges => this.DetectEdges, + Method.DrawImage => this.DrawImage, + Method.BinaryThreshold => this.BinaryThreshold, + Method.Histogram => this.Histogram, + Method.OilPaint => this.OilPaint, + _ => throw new NotImplementedException(), + }; + + Console.WriteLine(this.options); + Console.WriteLine($"Running {this.options.Method} for {this.options.Seconds} seconds ..."); + TimeSpan runFor = TimeSpan.FromSeconds(this.options.Seconds); + + // inFlight starts at 1 to represent the dispatch loop itself + int inFlight = 1; + TaskCompletionSource drainTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + + Stopwatch stopwatch = Stopwatch.StartNew(); + while (stopwatch.Elapsed < runFor && !drainTcs.Task.IsCompleted) + { + await semaphore.WaitAsync(); + + if (stopwatch.Elapsed >= runFor) + { + semaphore.Release(); + break; + } + + Interlocked.Increment(ref inFlight); + + _ = ProcessImage(); + + async Task ProcessImage() + { + try + { + if (stopwatch.Elapsed >= runFor || drainTcs.Task.IsCompleted) + { + return; + } + + await Task.Yield(); // "emulate IO", i.e., make sure the processing code is async + ulong pixels = (ulong)action(); + Interlocked.Add(ref this.totalProcessedPixels, pixels); + } + catch (Exception ex) + { + Console.WriteLine(ex); + drainTcs.TrySetException(ex); + } + finally + { + semaphore.Release(); + if (Interlocked.Decrement(ref inFlight) == 0) + { + drainTcs.TrySetResult(); + } + } + } + } + + // Release the dispatch loop's own count; if no work is in flight, this completes immediately + if (Interlocked.Decrement(ref inFlight) == 0) + { + drainTcs.TrySetResult(); + } + + await drainTcs.Task; + stopwatch.Stop(); + + double totalMegaPixels = this.totalProcessedPixels / 1_000_000.0; + double totalSeconds = stopwatch.ElapsedMilliseconds / 1000.0; + double megapixelsPerSec = totalMegaPixels / totalSeconds; + Console.WriteLine($"TotalSeconds: {totalSeconds:F2}"); + Console.WriteLine($"MegaPixelsPerSec: {megapixelsPerSec:F2}"); + } + + private int OilPaint() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.OilPaint()); + return image.Width * image.Height; + } + + private int DetectEdges() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.DetectEdges()); + return image.Width * image.Height; + } + + private int Crop() + { + using Image image = new(this.options.Width, this.options.Height); + Rectangle bounds = image.Bounds; + bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); + image.Clone(this.configuration, x => x.Crop(bounds)).Dispose(); + return image.Width * image.Height; + } + + private int DrawImage() + { + using Image image = new(this.options.Width, this.options.Height); + using Image foreground = new(this.options.Width, this.options.Height); + image.Mutate(c => c.DrawImage(foreground, 0.5f)); + return image.Width * image.Height; + } + + private int BinaryThreshold() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(c => c.BinaryThreshold(0.5f)); + return image.Width * image.Height; + } + + private int Histogram() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(c => c.HistogramEqualization()); + return image.Width * image.Height; + } + + private enum Method + { + Edges, + Crop, + DrawImage, + BinaryThreshold, + Histogram, + OilPaint + } + + private sealed class CommandLineOptions + { + [Option('m', "method", Required = false, Default = Method.Edges, HelpText = "The stress test method to run (Edges, Crop)")] + public Method Method { get; set; } = Method.Edges; + + [Option('p', "processor-parallelism", Required = false, Default = -1, HelpText = "Level of parallelism for the image processor")] + public int ProcessorParallelism { get; set; } = -1; + + [Option('c', "concurrent-requests", Required = false, Default = -1, HelpText = "Number of concurrent in-flight requests")] + public int ConcurrentRequests { get; set; } = -1; + + [Option('w', "width", Required = false, Default = 4000, HelpText = "Width of the test image")] + public int Width { get; set; } = 4000; + + [Option('h', "height", Required = false, Default = 4000, HelpText = "Height of the test image")] + public int Height { get; set; } = 4000; + + [Option('s', "seconds", Required = false, Default = 5, HelpText = "Duration of the stress test in seconds")] + public int Seconds { get; set; } = 5; + + public override string ToString() => string.Join( + "|", + $"method: {this.Method}", + $"processor-parallelism: {this.ProcessorParallelism}", + $"concurrent-requests: {this.ConcurrentRequests}", + $"width: {this.Width}", + $"height: {this.Height}", + $"seconds: {this.Seconds}"); + + public CommandLineOptions Normalize() + { + if (this.ProcessorParallelism < 0) + { + this.ProcessorParallelism = Environment.ProcessorCount; + } + + if (this.ConcurrentRequests < 0) + { + this.ConcurrentRequests = Environment.ProcessorCount; + } + + return this; + } + + public static CommandLineOptions Parse(string[] args) + { + CommandLineOptions result = null; + using Parser parser = new(settings => settings.CaseInsensitiveEnumValues = true); + ParserResult parserResult = parser.ParseArguments(args).WithParsed(o => + { + result = o; + }); + + if (result == null) + { + Console.WriteLine(HelpText.RenderUsageText(parserResult)); + } + + return result; + } + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 8ba862560..db8892cd7 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -1,76 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Reflection; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; +using SixLabors.ImageSharp.Tests.ProfilingSandbox; using Xunit.Abstractions; // in this file, comments are used for disabling stuff for local execution #pragma warning disable SA1515 #pragma warning disable SA1512 -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; +// LoadResizeSaveParallelMemoryStress.Run(args); +// ParallelProcessingStress.RunExperiment(args); +// ParallelProcessingStress.Run(args); +await ProcessorThroughputBenchmark.RunAsync(args); -public class Program -{ - private class ConsoleOutput : ITestOutputHelper - { - public void WriteLine(string message) => Console.WriteLine(message); - - public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - } - - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) - { - try - { - LoadResizeSaveParallelMemoryStress.Run(args); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - - // RunJpegEncoderProfilingTests(); - // RunJpegColorProfilingTests(); - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); - - // Console.ReadLine(); - } +// RunToVector4ProfilingTest(); +// RunResizeProfilingTest(); - private static Version GetNetCoreVersion() - { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - Console.WriteLine(assembly.Location); - string[] assemblyPath = assembly.Location.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) - { - return Version.Parse(assemblyPath[netCoreAppIndex + 1]); - } +static void RunResizeProfilingTest() +{ + ResizeProfilingBenchmarks test = new(new ConsoleOutput()); + test.ResizeBicubic(4000, 4000); +} - return null; - } +static void RunToVector4ProfilingTest() +{ + PixelOperationsTests.Rgba32_OperationsTests tests = new(new ConsoleOutput()); + tests.Benchmark_ToVector4(); +} - private static void RunResizeProfilingTest() - { - ResizeProfilingBenchmarks test = new(new ConsoleOutput()); - test.ResizeBicubic(4000, 4000); - } +sealed class ConsoleOutput : ITestOutputHelper +{ + public void WriteLine(string message) => Console.WriteLine(message); - private static void RunToVector4ProfilingTest() - { - PixelOperationsTests.Rgba32_OperationsTests tests = new(new ConsoleOutput()); - tests.Benchmark_ToVector4(); - } + public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); } From a9c43dc8007f12ff5fae38d6f37c070027a9cc8b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 10 Apr 2026 13:14:45 +1000 Subject: [PATCH 129/240] Convolution Replace parallel row iteration with sequential loops --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 114 +++++++++--------- .../Convolution2DProcessor{TPixel}.cs | 16 ++- .../Convolution2PassProcessor{TPixel}.cs | 33 +++-- .../ConvolutionProcessor{TPixel}.cs | 16 ++- .../EdgeDetectorCompassProcessor{TPixel}.cs | 12 +- ...ilterProcessor_BikeGrayscale_R16_C1_G3.png | 4 +- ...ilterProcessor_BikeGrayscale_R16_C2_G3.png | 4 +- ...FilterProcessor_BikeGrayscale_R8_C1_G1.png | 4 +- ...okehBlurFilterProcessor_Bike_R16_C1_G3.png | 4 +- ...okehBlurFilterProcessor_Bike_R16_C2_G3.png | 4 +- ...BokehBlurFilterProcessor_Bike_R8_C1_G1.png | 4 +- ...rProcessor_CalliphoraPartial_R16_C1_G3.png | 4 +- ...rProcessor_CalliphoraPartial_R16_C2_G3.png | 4 +- ...erProcessor_CalliphoraPartial_R8_C1_G1.png | 4 +- ...sor_Solid50x50_(255,0,0,255)_R16_C1_G3.png | 4 +- ...sor_Solid50x50_(255,0,0,255)_R16_C2_G3.png | 4 +- ...ssor_Solid50x50_(255,0,0,255)_R8_C1_G1.png | 4 +- ...Processor_TestPattern200x100_R16_C1_G3.png | 4 +- ...Processor_TestPattern200x100_R16_C2_G3.png | 4 +- ...rProcessor_TestPattern200x100_R8_C1_G1.png | 4 +- ...erProcessor_TestPattern23x31_R16_C1_G3.png | 4 +- ...erProcessor_TestPattern23x31_R16_C2_G3.png | 4 +- ...terProcessor_TestPattern23x31_R8_C1_G1.png | 4 +- ...erProcessor_TestPattern30x20_R16_C1_G3.png | 4 +- ...erProcessor_TestPattern30x20_R16_C2_G3.png | 4 +- ...terProcessor_TestPattern30x20_R8_C1_G1.png | 4 +- ...kehBlurFilterProcessor_cross_R16_C1_G3.png | 4 +- ...kehBlurFilterProcessor_cross_R16_C2_G3.png | 4 +- ...okehBlurFilterProcessor_cross_R8_C1_G1.png | 4 +- 29 files changed, 164 insertions(+), 123 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index a96fa1993..426544d69 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -77,22 +79,36 @@ internal class BokehBlurProcessor : ImageProcessor { Rectangle sourceRectangle = Rectangle.Intersect(this.SourceRectangle, source.Bounds); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. + // Preliminary gamma highlight pass if (this.gamma == 3F) { ApplyGamma3ExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); + + using IMemoryOwner gammaBuffer = allocator.Allocate(gammaOperation.GetRequiredBufferLength(sourceRectangle)); + Span gammaSpan = gammaBuffer.Memory.Span; + + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + gammaOperation.Invoke(y, gammaSpan); + } } else { ApplyGammaExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); + + using IMemoryOwner gammaBuffer = allocator.Allocate(gammaOperation.GetRequiredBufferLength(sourceRectangle)); + Span gammaSpan = gammaBuffer.Memory.Span; + + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + gammaOperation.Invoke(y, gammaSpan); + } } // Create a 0-filled buffer to use to store the result of the component convolutions @@ -105,18 +121,20 @@ internal class BokehBlurProcessor : ImageProcessor if (this.gamma == 3F) { ApplyInverseGamma3ExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); + + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + operation.Invoke(y); + } } else { - ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, 1 / this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); + ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, this.gamma); + + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + operation.Invoke(y); + } } } @@ -169,10 +187,15 @@ internal class BokehBlurProcessor : ImageProcessor kernel, configuration); - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in horizontalOperation); + using (IMemoryOwner hBuffer = configuration.MemoryAllocator.Allocate(horizontalOperation.GetRequiredBufferLength(sourceRectangle))) + { + Span hSpan = hBuffer.Memory.Span; + + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + horizontalOperation.Invoke(y, hSpan); + } + } // Vertical 1D convolutions to accumulate the partial results on the target buffer BokehBlurProcessor.SecondPassConvolutionRowOperation verticalOperation = new( @@ -184,10 +207,10 @@ internal class BokehBlurProcessor : ImageProcessor parameters.Z, parameters.W); - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in verticalOperation); + for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) + { + verticalOperation.Invoke(y); + } } } @@ -305,15 +328,9 @@ internal class BokehBlurProcessor : ImageProcessor { Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, (uint)x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } + // 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); } @@ -367,7 +384,7 @@ internal class BokehBlurProcessor : ImageProcessor private readonly Buffer2D targetPixels; private readonly Buffer2D sourceValues; private readonly Configuration configuration; - private readonly float inverseGamma; + private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] public ApplyInverseGammaExposureRowOperation( @@ -375,36 +392,26 @@ internal class BokehBlurProcessor : ImageProcessor Buffer2D targetPixels, Buffer2D sourceValues, Configuration configuration, - float inverseGamma) + float gamma) { this.bounds = bounds; this.targetPixels = targetPixels; this.sourceValues = sourceValues; this.configuration = configuration; - this.inverseGamma = inverseGamma; + this.gamma = gamma; } /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Vector4 low = Vector4.Zero; - Vector4 high = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y)[this.bounds.X..]; - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, (uint)x); - Vector4 clamp = Numerics.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } + Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, 1F); + GammaCompanding.Compress(sourceRowSpan, this.gamma); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Premultiply); } } @@ -433,17 +440,16 @@ internal class BokehBlurProcessor : ImageProcessor /// [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void Invoke(int y) + public void Invoke(int y) { Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); + Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, 1F); Numerics.CubeRootOnXYZ(sourceRowSpan); Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 02e06db49..4ed6934a2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Numerics; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -79,10 +79,16 @@ internal class Convolution2DProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in operation); + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. + using IMemoryOwner buffer = allocator.Allocate(operation.GetRequiredBufferLength(interest)); + Span span = buffer.Memory.Span; + + for (int y = interest.Top; y < interest.Bottom; y++) + { + operation.Invoke(y, span); + } } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1bbbdb350..6d1d7c23c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -106,6 +107,12 @@ internal class Convolution2PassProcessor : ImageProcessor mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. + // Horizontal convolution HorizontalConvolutionRowOperation horizontalOperation = new( interest, @@ -116,10 +123,15 @@ internal class Convolution2PassProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in horizontalOperation); + using (IMemoryOwner hBuffer = allocator.Allocate(horizontalOperation.GetRequiredBufferLength(interest))) + { + Span hSpan = hBuffer.Memory.Span; + + for (int y = interest.Top; y < interest.Bottom; y++) + { + horizontalOperation.Invoke(y, hSpan); + } + } // Vertical convolution VerticalConvolutionRowOperation verticalOperation = new( @@ -131,10 +143,15 @@ internal class Convolution2PassProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in verticalOperation); + using (IMemoryOwner vBuffer = allocator.Allocate(verticalOperation.GetRequiredBufferLength(interest))) + { + Span vSpan = vBuffer.Memory.Span; + + for (int y = interest.Top; y < interest.Bottom; y++) + { + verticalOperation.Invoke(y, vSpan); + } + } } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index feaaf30ce..b704f556f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -96,10 +97,17 @@ internal class ConvolutionProcessor : ImageProcessor map.BuildSamplingOffsetMap(this.KernelXY.Rows, this.KernelXY.Columns, interest, this.BorderWrapModeX, this.BorderWrapModeY); RowOperation operation = new(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); + + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. + using IMemoryOwner buffer = allocator.Allocate(operation.GetRequiredBufferLength(interest)); + Span span = buffer.Memory.Span; + + for (int y = interest.Top; y < interest.Bottom; y++) + { + operation.Invoke(y, span); + } } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index eae748166..f1edb75a2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -83,11 +83,15 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor processor.Apply(pass); } + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. RowOperation operation = new(source.PixelBuffer, pass.PixelBuffer, interest); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); + + for (int y = interest.Top; y < interest.Bottom; y++) + { + operation.Invoke(y); + } } } diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png index 564a76f90..3b4c12a24 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3a6e47d880b7276702e6bf4b1ba1b4781abfa2cd99ee9321a56169440fc318c -size 49844 +oid sha256:0c047a274060b1bf73af9c922bff4f2ea627bd0fad023b3c3a5852f49d026c6b +size 50025 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png index 4f2dc77b0..7dad93fba 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b4837dceb9a065c5b27133b67ef257a0f050fa1342d02a7e06d3501e068f47 -size 44966 +oid sha256:aa1b0b58ddb6a5c29ccd78f1bc2e773a2005493b92efe5c0deb8056af6fc0208 +size 45605 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png index 57ce8295e..a9b287c6b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e652706e1aec475c8eec08392d65be1bd888cc79eccc4019eb3826db71c6dac0 -size 54744 +oid sha256:ead63aaaf5b185342f83317c2400b7ada29a82223e35e9a7382f690101ae40f7 +size 54798 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png index a0e9c1b24..a4ac9718a 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffc82427285a2bfeb3226e3e87ac316e41a2a8f853bb406b88ba2ae7bfae099d -size 164923 +oid sha256:025751d22eeeac10878e79d2bf1987e0a055cf9f54903048673189a385036744 +size 157237 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png index caf152f9b..2c18a147d 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ea08065be7271e7ff75ed49e63544701bc14a03bd44d754fb056370559dd105 -size 149483 +oid sha256:77cae565842c0c8b0314d1c10c72f4b52483d002c614a33734b534f743d06b5d +size 146574 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png index 5ad60a4bf..0e31e223b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a61bca9de7e0b6bf6f88c4d01b9e3f1611f6866db10e4a1168550ab84804f42 -size 178531 +oid sha256:223027dcd3a8e5ded5f98b3aa47b0e18bc40b569e47f6bfdab1126f4420e5977 +size 171283 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png index d1b15eb8e..be4b535c7 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e2f6c6c42ddd15c0730edcd402bf8fd05a1116cb72495e3156a545988ff0230 -size 85901 +oid sha256:db64cd4e0369fa2db33aa1f21a94466ca39a191b0e0adbbbfbcbca44284c61ab +size 86151 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png index 3acb240b3..5ddb7ce12 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8193ce8690dbb99517cd56e5a866268c0a5c971c992058afb818393658781b91 -size 77762 +oid sha256:afc3a020178a68715ab0dafa30ee1db81630b760fde3df3271048acc627f178c +size 77614 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png index 335375205..db03e0fb4 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:412c302f92500d855658d466aa1a686386ed998f6a3d5fe7326cd148a6f829ae -size 116229 +oid sha256:5ea7e0c3e16acf40491354191278a09af2db5ac6e0e7b92ba377f8fc9d52e305 +size 117067 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png index e155d618d..9edb930aa 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:570cb16b4026d38cbf586592c34d0fe303f2c1d99baeb531670c4ba25956f9c3 -size 15489 +oid sha256:20bb78a7539049ced92c936c2a78fdeb3809ffe0cb1dfae034e55e326de094a8 +size 14285 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png index 3f93014b6..ce093b38a 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27813f975640c7ac15e78af910efc796ada950cd7efcd9a4fe045d531de24ec8 -size 15197 +oid sha256:a55e413991f9817ce9cecaf9d3ed31f5cd7559f9d527ff2e03c8bea5be091887 +size 13943 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png index 198baadf7..a8d549dc6 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e10a91175585b3843b25b710dc45f734ba706b60fc56a21748da355f27b8dcf5 -size 12776 +oid sha256:2afdab1f7f430c3f38fa7a6cdac60fbeb03ebcdda00a1bc79b289680a6811388 +size 12056 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png index 9140cdb0b..5104bad4b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c0aca87ea94ec94c4d9e458dd33d366e0166533ee3e3c39066ee9d8be63a74e -size 1393 +oid sha256:028cd8201f9e7cd4988d63cc80d9afc96d0c739c65d807f7c755947e49e65111 +size 1326 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png index 32778c0a7..f96d77928 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d486512f51a57c90de6c3126791fbd3b7e7dd756932b5bd716660ee9b72f010 -size 1168 +oid sha256:213e3bd008292b807e2a2ec216ec017a31cd165f4d2a8bc86b3deea3daffc81c +size 1109 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png index a60c7f0cb..6656f7564 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79f344abd9f1cfe6e8c2f0be52fd9498ee3ee6370cea94244d90fdaba7fa5e71 -size 1488 +oid sha256:2ceeb731111403f6354c460e403a30a2269ae1b2e2a9ef9b3819bdcadb50b859 +size 1424 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png index ae9b64afa..da9a0c2e3 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90a45e7212ce97754be0e1432fffdc62ef5d2bdc30fdff0477a1951754cc0929 -size 1113 +oid sha256:fa9d77a2f1b727a42323a8ac19cb4cabdef0e7f0257bc92bb0100cd280f87613 +size 1076 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png index 1e9dc61e6..e0ccdbb5d 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c61766f05dc48413c21baea8af1994895aa354732109fd5701a42f746f1f412 -size 1005 +oid sha256:fc9986cd6042d8cad709a5768dc464fc171a3cb5d3457406c11c46ef55388daf +size 956 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png index 99a249b1d..4f1d40e2c 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be002593c2874eab803d0dc9b5646fb51b94ea92e17158797609219f46c2f723 -size 1518 +oid sha256:7e92efee845abd30705bb9a9cf61997092fca89b48e20d8383a0c08ab159a0af +size 1491 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png index d4ff4e16b..404dc83b1 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11edba36c7f73271d9575b16cc7663a1e75c3b34560df51b7430cfde3ce1ea08 -size 6576 +oid sha256:21e8dce4256aecb250e0079c8bd60deb8b2ca55848fb0fd647ed3115afcf82af +size 8401 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png index a02c2d670..525eedcd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75320c25e8977e5d56ff32f054e15773bd797e4ba745cd7bf3a2fd4c7000e3df -size 6400 +oid sha256:c4b1b7f904b23b6d7bd7b0fc4c533d3c213f38bcab689e9dac1936b05b71e9cf +size 8268 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png index 5fbabed0a..2585ff479 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb334d24245359fda8b64a74cbd30267cbdd1238e0aeae674b57be5371cd7db -size 6266 +oid sha256:33ca697815ebf4c74343e0abc7a418af8ffdd8fa0830285770249a1f20929eae +size 8444 From abafe100cd30a8e9ee510b26fd194826fee6d6eb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 10 Apr 2026 13:18:54 +1000 Subject: [PATCH 130/240] Use sequential row processing in CropProcessor --- .../Processors/Transforms/CropProcessor{TPixel}.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 5ef2422a3..dd992df8c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -59,16 +59,14 @@ internal class CropProcessor : TransformProcessor Rectangle bounds = this.cropRectangle; - // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = - ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - + // Copying is too cheap to benefit from parallelization; + // the overhead exceeds the work per task. See #3111. RowOperation operation = new(bounds, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - bounds, - in parallelSettings, - in operation); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + operation.Invoke(y); + } } /// From 0220108d86f7c0986257abb91029f182541bcf55 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 10 Apr 2026 13:48:58 +1000 Subject: [PATCH 131/240] Use ParallelRowIterator for BokehBlur --- .../Convolution/BokehBlurProcessor{TPixel}.cs | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 426544d69..663d54afc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -79,36 +78,22 @@ internal class BokehBlurProcessor : ImageProcessor { Rectangle sourceRectangle = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - - // Convolution is memory-bandwidth-bound with low arithmetic intensity. - // Parallelization degrades performance due to cache line contention from - // overlapping source row reads. See #3111. - // Preliminary gamma highlight pass if (this.gamma == 3F) { ApplyGamma3ExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration); - - using IMemoryOwner gammaBuffer = allocator.Allocate(gammaOperation.GetRequiredBufferLength(sourceRectangle)); - Span gammaSpan = gammaBuffer.Memory.Span; - - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - gammaOperation.Invoke(y, gammaSpan); - } + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in gammaOperation); } else { ApplyGammaExposureRowOperation gammaOperation = new(sourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - - using IMemoryOwner gammaBuffer = allocator.Allocate(gammaOperation.GetRequiredBufferLength(sourceRectangle)); - Span gammaSpan = gammaBuffer.Memory.Span; - - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - gammaOperation.Invoke(y, gammaSpan); - } + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in gammaOperation); } // Create a 0-filled buffer to use to store the result of the component convolutions @@ -121,20 +106,18 @@ internal class BokehBlurProcessor : ImageProcessor if (this.gamma == 3F) { ApplyInverseGamma3ExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration); - - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - operation.Invoke(y); - } + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in operation); } else { ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, this.gamma); - - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - operation.Invoke(y); - } + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in operation); } } @@ -187,15 +170,10 @@ internal class BokehBlurProcessor : ImageProcessor kernel, configuration); - using (IMemoryOwner hBuffer = configuration.MemoryAllocator.Allocate(horizontalOperation.GetRequiredBufferLength(sourceRectangle))) - { - Span hSpan = hBuffer.Memory.Span; - - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - horizontalOperation.Invoke(y, hSpan); - } - } + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); // Vertical 1D convolutions to accumulate the partial results on the target buffer BokehBlurProcessor.SecondPassConvolutionRowOperation verticalOperation = new( @@ -207,10 +185,10 @@ internal class BokehBlurProcessor : ImageProcessor parameters.Z, parameters.W); - for (int y = sourceRectangle.Top; y < sourceRectangle.Bottom; y++) - { - verticalOperation.Invoke(y); - } + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in verticalOperation); } } From 51463a368da6fcb7cb8c5dd353e9dc9377b5a39d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 Apr 2026 19:14:23 +0200 Subject: [PATCH 132/240] Better test image for uint pixel type --- tests/ImageSharp.Tests/TestImages.cs | 2 +- ...ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png | 3 +++ ...ed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png | 3 --- tests/Images/Input/Exr/rgb_uint32_uncompressed.exr | 3 --- 4 files changed, 4 insertions(+), 7 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png delete mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png delete mode 100644 tests/Images/Input/Exr/rgb_uint32_uncompressed.exr diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 51e5eb510..210bd9ea4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1388,7 +1388,7 @@ public static class TestImages public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; - public const string UncompressedUintRgb = "Exr/rgb_uint32_uncompressed.exr"; + public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png new file mode 100644 index 000000000..8f31f0d8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942 +size 163327 diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png deleted file mode 100644 index d27a842a0..000000000 --- a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_rgb_uint32_uncompressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15adaff9755ff38f0a2b30180ced9e52ac1bf51b8f3b80fd26d85f18c0eef686 -size 68918 diff --git a/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr b/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr deleted file mode 100644 index 9a652749f..000000000 --- a/tests/Images/Input/Exr/rgb_uint32_uncompressed.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:487f4641908b0c5c34e7cc3be439eff31e5e8d2fd03a9d17d39dc3dc5e55874d -size 243558 From b4cd27f2c9807832757944c7662fd52e8e8af54e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 Apr 2026 19:22:05 +0200 Subject: [PATCH 133/240] Remove unnecessary ColorScaleTo32Bit when decoding pixel type uint32 --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 3 ++- tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 7598c62f3..612031428 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -226,7 +226,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int x = 0; x < width; x++) { - pixelRow[x] = ColorScaleTo32Bit(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); + Rgb96 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x]); + pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); } decodedRows++; diff --git a/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr new file mode 100644 index 000000000..867cffaa1 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bed68d2e491cad0bdd0fa668ce57328951927d722f130ba7c5c5475f6a3e0d96 +size 289183 From 5926455cefe3a48a4668719a5b7db73919900f48 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 Apr 2026 19:34:17 +0200 Subject: [PATCH 134/240] Remove ExrCompression property from ExrBaseCompression, it is not needed --- .../Exr/Compression/Compressors/NoneExrCompressor.cs | 4 ---- .../Formats/Exr/Compression/Compressors/ZipExrCompressor.cs | 4 ---- src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs | 6 ------ 3 files changed, 14 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index cd9bec613..21a50c531 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; @@ -13,9 +12,6 @@ internal class NoneExrCompressor : ExrBaseCompressor { } - /// - public override ExrCompression Method => ExrCompression.Zip; - /// public override uint CompressRowBlock(Span rows, int rowCount) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index dcfbe3ae0..d24b8f7dd 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; @@ -23,9 +22,6 @@ internal class ZipExrCompressor : ExrBaseCompressor this.memoryStream = new(); } - /// - public override ExrCompression Method => ExrCompression.Zip; - /// public override uint CompressRowBlock(Span rows, int rowCount) { diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index 1f464ec67..1dcdec5be 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; @@ -19,11 +18,6 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression : base(allocator, bytesPerBlock, bytesPerRow) => this.Output = output; - /// - /// Gets the compression method to use. - /// - public abstract ExrCompression Method { get; } - /// /// Gets the output stream to write the compressed image to. /// From 0ceeba4461702636559b735eb07962b2cb106925 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Apr 2026 17:13:29 +0200 Subject: [PATCH 135/240] Remove no longer needed ColorScaleTo32Bit() --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 612031428..62d3591f5 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Text; using SixLabors.ImageSharp.Formats.Exr.Compression; @@ -21,8 +20,6 @@ namespace SixLabors.ImageSharp.Formats.Exr; /// internal sealed class ExrDecoderCore : ImageDecoderCore { - private const float Scale32Bit = 1f / 0xFFFFFFFF; - /// /// Reusable buffer. /// @@ -814,9 +811,4 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return Unsafe.As(ref intValue); } - - [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); } From 29f558a801c8eab05ebe8ba422d79bf3679d1a54 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Apr 2026 17:23:39 +0200 Subject: [PATCH 136/240] Add test case for uint pixel type with alpha --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- .../Formats/Exr/ExrDecoderTests.cs | 14 ++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + ...elType_Uint_Rgba32_rgba_uint_zip_compressed.png | 3 +++ .../Images/Input/Exr/rgba_uint_zip_compressed.exr | 3 +++ 5 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png create mode 100644 tests/Images/Input/Exr/rgba_uint_zip_compressed.exr diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 62d3591f5..a4fa4b0ac 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -223,7 +223,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int x = 0; x < width; x++) { - Rgb96 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x]); + Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); pixelRow[x] = TPixel.FromVector4(pixelValue.ToVector4()); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index e6a00c84d..b81dba755 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -55,6 +55,20 @@ public class ExrDecoderTests Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); } + [Theory] + [WithFile(TestImages.Exr.UintRgba, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); + image.DebugSave(provider); + + // Compare to referene output, since the reference decoder does not support this pixel type. + image.CompareToReferenceOutput(provider); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); + } + [Theory] [WithFile(TestImages.Exr.Rgb, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Rgb(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 210bd9ea4..343024785 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1389,6 +1389,7 @@ public static class TestImages public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr"; + public const string UintRgba = "Exr/rgba_uint_zip_compressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png new file mode 100644 index 000000000..26de0f2d1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:843bea4db378f52935e2f19f60d289df8ebe20ddde3977c63225f1d58a10bd62 +size 48119 diff --git a/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr b/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr new file mode 100644 index 000000000..bdcd9e1ff --- /dev/null +++ b/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff2ff1cdcfd219415430ba30a18962b54860d88bfc3adf337e97574c33c5e214 +size 119614 From 372299744aa47bd5974d82857a87b00f1774231a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Apr 2026 19:19:57 +0200 Subject: [PATCH 137/240] Fix mistake when encoding uint pixel data: value from Rgb96.FromVector4 was not used --- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 6f11bd9f4..85179ee7c 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -267,7 +267,7 @@ internal sealed class ExrEncoderCore for (int x = 0; x < width; x++) { Vector4 vector4 = pixelRowSpan[x].ToVector4(); - Rgb96.FromVector4(vector4); + rgb = Rgb96.FromVector4(vector4); redBuffer[x] = rgb.R; greenBuffer[x] = rgb.G; From 15701e5e84040b4008fea79050fdecdd46bd72de Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Apr 2026 19:36:30 +0200 Subject: [PATCH 138/240] Write alpha channel when encoding exr images --- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 43 +++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 85179ee7c..9f86e79e6 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -90,6 +90,7 @@ internal sealed class ExrEncoderCore int screenWindowWidth = 1; List channels = [ + new(ExrConstants.ChannelNames.Alpha, this.pixelType.Value, 0, 1, 1), new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), @@ -160,11 +161,12 @@ internal sealed class ExrEncoderCore uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); Span redBuffer = rgbBuffer.GetSpan()[..width]; Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); @@ -191,6 +193,7 @@ internal sealed class ExrEncoderCore redBuffer[x] = vector4.X; greenBuffer[x] = vector4.Y; blueBuffer[x] = vector4.Z; + alphaBuffer[x] = vector4.W; } // Write pixel data to row block buffer. @@ -198,10 +201,10 @@ internal sealed class ExrEncoderCore switch (this.pixelType) { case ExrPixelType.Float: - WriteSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); break; case ExrPixelType.Half: - WriteHalfSingleRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteHalfSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); break; } @@ -238,15 +241,16 @@ internal sealed class ExrEncoderCore uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); uint bytesPerBlock = bytesPerRow * rowsPerBlock; - using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 3, AllocationOptions.Clean); + using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); Span redBuffer = rgbBuffer.GetSpan()[..width]; Span greenBuffer = rgbBuffer.GetSpan().Slice(width, width); Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); + Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); - Rgb96 rgb = default; + Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; for (uint y = 0; y < height; y += rowsPerBlock) { @@ -267,16 +271,17 @@ internal sealed class ExrEncoderCore for (int x = 0; x < width; x++) { Vector4 vector4 = pixelRowSpan[x].ToVector4(); - rgb = Rgb96.FromVector4(vector4); + rgb = Rgba128.FromVector4(vector4); redBuffer[x] = rgb.R; greenBuffer[x] = rgb.G; blueBuffer[x] = rgb.B; + alphaBuffer[x] = rgb.A; } // Write row data to row block buffer. Span rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); - WriteUnsignedIntRow(rowBlockSpan, width, blueBuffer, greenBuffer, redBuffer); + WriteUnsignedIntRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); rowsInBlockCount++; } @@ -309,9 +314,15 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } - private static void WriteSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteSingleRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; + for (int x = 0; x < width; x++) + { + WriteSingleToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); + offset += 4; + } + for (int x = 0; x < width; x++) { WriteSingleToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); @@ -331,9 +342,15 @@ internal sealed class ExrEncoderCore } } - private static void WriteHalfSingleRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteHalfSingleRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; + for (int x = 0; x < width; x++) + { + WriteHalfSingleToBuffer(buffer.Slice(offset, 2), alphaBuffer[x]); + offset += 2; + } + for (int x = 0; x < width; x++) { WriteHalfSingleToBuffer(buffer.Slice(offset, 2), blueBuffer[x]); @@ -353,9 +370,15 @@ internal sealed class ExrEncoderCore } } - private static void WriteUnsignedIntRow(Span buffer, int width, Span blueBuffer, Span greenBuffer, Span redBuffer) + private static void WriteUnsignedIntRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; + for (int x = 0; x < width; x++) + { + WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); + offset += 4; + } + for (int x = 0; x < width; x++) { WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); From af14463b07d27dcad38d3a4f1e4d6ba24e2676ed Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Apr 2026 19:53:01 +0200 Subject: [PATCH 139/240] Add better test image for uint rgba pixel type --- tests/ImageSharp.Tests/TestImages.cs | 2 +- ...ed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png | 3 +++ ..._Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png | 3 --- tests/Images/Input/Exr/rgba_uint_uncompressed.exr | 3 +++ tests/Images/Input/Exr/rgba_uint_zip_compressed.exr | 3 --- 5 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png delete mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png create mode 100644 tests/Images/Input/Exr/rgba_uint_uncompressed.exr delete mode 100644 tests/Images/Input/Exr/rgba_uint_zip_compressed.exr diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 343024785..f7e130d66 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1389,7 +1389,7 @@ public static class TestImages public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr"; - public const string UintRgba = "Exr/rgba_uint_zip_compressed.exr"; + public const string UintRgba = "Exr/rgba_uint_uncompressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; 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 new file mode 100644 index 000000000..f705b63df --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:107c6d26cdb8aac539011acf7213376e930cb705f5815b010b31ce4b9e738f9b +size 25780 diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png deleted file mode 100644 index 26de0f2d1..000000000 --- a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_zip_compressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:843bea4db378f52935e2f19f60d289df8ebe20ddde3977c63225f1d58a10bd62 -size 48119 diff --git a/tests/Images/Input/Exr/rgba_uint_uncompressed.exr b/tests/Images/Input/Exr/rgba_uint_uncompressed.exr new file mode 100644 index 000000000..2738783b2 --- /dev/null +++ b/tests/Images/Input/Exr/rgba_uint_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b45345cb1e4539e5298ccce3ccfc0f27b7a469836bfd7aa13c4d9f6c52baa7a +size 482731 diff --git a/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr b/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr deleted file mode 100644 index bdcd9e1ff..000000000 --- a/tests/Images/Input/Exr/rgba_uint_zip_compressed.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff2ff1cdcfd219415430ba30a18962b54860d88bfc3adf337e97574c33c5e214 -size 119614 From e405df597f9cc673e14327188eaf42b018b4d696 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Apr 2026 12:50:38 +1000 Subject: [PATCH 140/240] Update settings --- .../Advanced/ParallelExecutionSettings.cs | 14 ++++++-------- src/ImageSharp/Configuration.cs | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs index ad0318297..cf86d094d 100644 --- a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -20,7 +20,7 @@ public readonly struct ParallelExecutionSettings /// /// /// The value used for initializing when using TPL. - /// Set to -1 to leave the degree of parallelism unbounded. + /// If set to -1, there is no limit on the number of concurrently running operations. /// /// The value for . /// The . @@ -31,7 +31,7 @@ public readonly struct ParallelExecutionSettings { // Shall be compatible with ParallelOptions.MaxDegreeOfParallelism: // https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism - if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1) + if (maxDegreeOfParallelism is 0 or < -1) { throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); } @@ -49,7 +49,7 @@ public readonly struct ParallelExecutionSettings /// /// /// The value used for initializing when using TPL. - /// Set to -1 to leave the degree of parallelism unbounded. + /// If set to -1, there is no limit on the number of concurrently running operations. /// /// The . public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) @@ -64,7 +64,7 @@ public readonly struct ParallelExecutionSettings /// /// Gets the value used for initializing when using TPL. - /// A value of -1 leaves the degree of parallelism unbounded. + /// A value of -1 means there is no limit on the number of concurrently running operations. /// public int MaxDegreeOfParallelism { get; } @@ -93,12 +93,10 @@ public readonly struct ParallelExecutionSettings } /// - /// Get the default for a + /// Get the default for a /// /// The . /// The . public static ParallelExecutionSettings FromConfiguration(Configuration configuration) - { - return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); - } + => new(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 267392723..ae6fd9661 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -64,8 +64,9 @@ public sealed class Configuration /// /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms /// configured with this instance. - /// Set to -1 to leave the degree of parallelism unbounded. - /// Initialized with by default. + /// A positive value limits the number of concurrent operations to the set value. + /// If set to -1, there is no limit on the number of concurrently running operations. + /// Defaults to . /// public int MaxDegreeOfParallelism { From 656f53d05297bedb61902bfd9b9e2962c1c7308d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 00:02:11 +1000 Subject: [PATCH 141/240] Remove not-needed validate method and tests --- .../Advanced/ParallelRowIterator.cs | 35 ------------------ .../Helpers/ParallelRowIteratorTests.cs | 36 ------------------- 2 files changed, 71 deletions(-) diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 98c2656d1..f404326bc 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -44,7 +44,6 @@ public static partial class ParallelRowIterator where T : struct, IRowOperation { ValidateRectangle(rectangle); - ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; @@ -109,7 +108,6 @@ public static partial class ParallelRowIterator where TBuffer : unmanaged { ValidateRectangle(rectangle); - ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; @@ -174,7 +172,6 @@ public static partial class ParallelRowIterator where T : struct, IRowIntervalOperation { ValidateRectangle(rectangle); - ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; @@ -236,7 +233,6 @@ public static partial class ParallelRowIterator where TBuffer : unmanaged { ValidateRectangle(rectangle); - ValidateSettings(parallelSettings); int top = rectangle.Top; int bottom = rectangle.Bottom; @@ -315,35 +311,4 @@ public static partial class ParallelRowIterator 0, $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); } - - /// - /// Validates the supplied . - /// - /// The execution settings. - /// - /// Thrown when or - /// is invalid. - /// - /// - /// Thrown when is null. - /// This also guards the public default value, which bypasses constructor validation. - /// - private static void ValidateSettings(in ParallelExecutionSettings parallelSettings) - { - // ParallelExecutionSettings is a public struct, so callers can pass default and bypass constructor validation. - if (parallelSettings.MaxDegreeOfParallelism is 0 or < -1) - { - throw new ArgumentOutOfRangeException( - $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MaxDegreeOfParallelism)}"); - } - - Guard.MustBeGreaterThan( - parallelSettings.MinimumPixelsProcessedPerTask, - 0, - $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MinimumPixelsProcessedPerTask)}"); - - Guard.NotNull( - parallelSettings.MemoryAllocator, - $"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MemoryAllocator)}"); - } } diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index cf68f702a..017926fc5 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -224,24 +224,6 @@ public class ParallelRowIteratorTests Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); } - [Fact] - public void IterateRowsWithTempBuffer_DefaultSettingsRequireInitialization() - { - ParallelExecutionSettings parallelSettings = default; - Rectangle rect = new(0, 0, 10, 10); - - void RowAction(int y, Span memory) - { - } - - TestRowOperation operation = new(RowAction); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows, Rgba32>(rect, in parallelSettings, in operation)); - - Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message); - } - public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new() { @@ -367,24 +349,6 @@ public class ParallelRowIteratorTests Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); } - [Fact] - public void IterateRows_DefaultSettingsRequireInitialization() - { - ParallelExecutionSettings parallelSettings = default; - Rectangle rect = new(0, 0, 10, 10); - - void RowAction(int y) - { - } - - TestRowActionOperation operation = new(RowAction); - - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation)); - - Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message); - } - public static readonly TheoryData IterateRectangularBuffer_Data = new() { From 1e69aa45ab4eaed35397d118770726fdc3af702f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 Apr 2026 18:35:44 +0200 Subject: [PATCH 142/240] Add additional doc strings --- .../Compressors/NoneExrCompressor.cs | 10 + .../Compressors/ZipExrCompressor.cs | 11 + .../Decompressors/B44ExrCompression.cs | 44 +++- .../Decompressors/NoneExrCompression.cs | 11 +- .../Decompressors/RunLengthExrCompression.cs | 16 +- .../Decompressors/ZipExrCompression.cs | 11 +- .../Exr/Compression/ExrBaseCompression.cs | 9 + .../Exr/Compression/ExrBaseDecompressor.cs | 27 +++ .../Exr/Compression/ExrCompressorFactory.cs | 33 +-- .../Exr/Compression/ExrDecompressorFactory.cs | 39 ++-- .../Formats/Exr/Constants/ExrImageType.cs | 10 + .../Formats/Exr/Constants/ExrLineOrder.cs | 12 + src/ImageSharp/Formats/Exr/ExrAttribute.cs | 18 ++ src/ImageSharp/Formats/Exr/ExrBox2i.cs | 22 ++ src/ImageSharp/Formats/Exr/ExrChannelInfo.cs | 35 ++- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 215 ++++++++++++++---- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 186 +++++++++++++-- .../Formats/Exr/ExrHeaderAttributes.cs | 14 ++ 18 files changed, 606 insertions(+), 117 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index 21a50c531..58768e990 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -5,8 +5,18 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; +/// +/// Compressor for EXR image data which does not use any compression method. +/// internal class NoneExrCompressor : ExrBaseCompressor { + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image data to. + /// The memory allocator. + /// Bytes per row block. + /// Bytes per pixel row. public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) : base(output, allocator, bytesPerBlock, bytesPerRow) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index d24b8f7dd..ef7285da0 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -6,6 +6,9 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; +/// +/// Compressor for EXR image data using the ZIP compression. +/// internal class ZipExrCompressor : ExrBaseCompressor { private readonly DeflateCompressionLevel compressionLevel; @@ -14,6 +17,14 @@ internal class ZipExrCompressor : ExrBaseCompressor private readonly System.Buffers.IMemoryOwner buffer; + /// + /// Initializes a new instance of the class. + /// + /// The stream to write the compressed data to. + /// The memory allocator. + /// The bytes per block. + /// The bytes per row. + /// The compression level for deflate compression. public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel) : base(output, allocator, bytesPerBlock, bytesPerRow) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index e5b735a39..aa4944649 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -8,6 +8,9 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Implementation of B44 decompressor for EXR image data. +/// internal class B44ExrCompression : ExrBaseDecompressor { private readonly int width; @@ -22,6 +25,15 @@ internal class B44ExrCompression : ExrBaseDecompressor private readonly IMemoryOwner tmpBuffer; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per row. + /// The rows per block. + /// The width of a pixel row in pixels. + /// The number of channels of the image. public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) : base(allocator, bytesPerBlock, bytesPerRow) { @@ -57,7 +69,7 @@ internal class B44ExrCompression : ExrBaseDecompressor int bytesRead = stream.Read(this.scratch, 0, 3); if (bytesRead == 0) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } if (this.scratch[2] >= 13 << 2) @@ -70,7 +82,7 @@ internal class B44ExrCompression : ExrBaseDecompressor bytesRead = stream.Read(this.scratch, 3, 11); if (bytesRead == 0) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } Unpack14(this.scratch, this.s); @@ -80,22 +92,22 @@ internal class B44ExrCompression : ExrBaseDecompressor int n = x + 3 < this.width ? 4 : this.width - x; if (y + 3 < this.rowsPerBlock) { - this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); - this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); - this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); - this.s.AsSpan(12, n).CopyTo(row3.Slice(rowOffset)); + this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); + this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); + this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); + this.s.AsSpan(12, n).CopyTo(row3[rowOffset..]); } else { - this.s.AsSpan(0, n).CopyTo(row0.Slice(rowOffset)); + this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); if (y + 1 < this.rowsPerBlock) { - this.s.AsSpan(4, n).CopyTo(row1.Slice(rowOffset)); + this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); } if (y + 2 < this.rowsPerBlock) { - this.s.AsSpan(8, n).CopyTo(row2.Slice(rowOffset)); + this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); } } @@ -117,7 +129,7 @@ internal class B44ExrCompression : ExrBaseDecompressor { for (int i = 0; i < this.channelCount; i++) { - decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer.Slice(offsetOutput)); + decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer[offsetOutput..]); offsetOutput += this.width; } @@ -125,7 +137,11 @@ internal class B44ExrCompression : ExrBaseDecompressor } } - // Unpack a 14-byte block into 4 by 4 16-bit pixels. + /// + /// Unpack a 14-byte block into 4 by 4 16-bit pixels. + /// + /// The source byte data to unpack. + /// Destintation buffer. private static void Unpack14(Span b, Span s) { s[0] = (ushort)((b[0] << 8) | b[1]); @@ -165,7 +181,11 @@ internal class B44ExrCompression : ExrBaseDecompressor } } - // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels. + /// + /// // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels. + /// + /// The source byte data to unpack. + /// The destination buffer. private static void Unpack3(Span b, Span s) { s[0] = (ushort)((b[0] << 8) | b[1]); diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index fb4fa5d83..c15ffbe32 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -6,8 +6,17 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Decompressor for EXR image data which do not use any compression. +/// internal class NoneExrCompression : ExrBaseDecompressor { + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per pixel row. public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) : base(allocator, bytesPerBlock, bytesPerRow) { @@ -19,7 +28,7 @@ internal class NoneExrCompression : ExrBaseDecompressor int bytesRead = stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); if (bytesRead != (int)this.BytesPerBlock) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data!"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data from the stream!"); } } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 12f5fc8ab..489493d82 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -7,12 +7,21 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Implementation of RLE decompressor for EXR images. +/// internal class RunLengthExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; private readonly ushort[] s = new ushort[16]; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per row. public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); @@ -68,12 +77,17 @@ internal class RunLengthExrCompression : ExrBaseDecompressor Interleave(uncompressed, this.BytesPerBlock, buffer); } + /// + /// Reads the next byte from the stream. + /// + /// The stream. + /// The next byte. private static byte ReadNextByte(BufferedReadStream stream) { int nextByte = stream.ReadByte(); if (nextByte == -1) { - ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE image!"); + ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE encoded EXR image!"); } return (byte)nextByte; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index b8dc5efa8..dafe3d832 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -9,10 +9,19 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Implementation of zhe Zip decompressor for EXR image data. +/// internal class ZipExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per pixel row. public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); @@ -46,7 +55,7 @@ internal class ZipExrCompression : ExrBaseDecompressor if (totalRead == 0) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read zip compressed image data!"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); } Reconstruct(uncompressed, (uint)totalRead); diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 57e6b2a26..b116dffc6 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -5,10 +5,19 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; +/// +/// Base class for EXR compression. +/// internal abstract class ExrBaseCompression : IDisposable { private bool isDisposed; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per block. + /// The bytes per row. protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) { this.Allocator = allocator; diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 1bbf36d76..efe3c7877 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -6,15 +6,36 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; +/// +/// The base EXR decompressor class. +/// internal abstract class ExrBaseDecompressor : ExrBaseCompression { + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per row block. + /// The bytes per row. protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) : base(allocator, bytesPerBlock, bytesPerRow) { } + /// + /// Decompresses the specified stream. + /// + /// The buffered stream to decompress. + /// The compressed bytes. + /// The buffer to write the decompressed data to. public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + /// + /// Integrate over all differences to the previous value in order to + /// reconstruct sample values. + /// + /// The buffer with the data. + /// The un compressed bytes. protected static void Reconstruct(Span buffer, uint unCompressedBytes) { int offset = 0; @@ -26,6 +47,12 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression } } + /// + /// Interleaves the input data. + /// + /// The source data. + /// The uncompressed bytes. + /// The output to write to. protected static void Interleave(Span source, uint unCompressedBytes, Span output) { int sourceOffset = 0; diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index 24f396e16..b1b7d2e8e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -8,27 +8,32 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; +/// +/// Factory class for creating a compressor for EXR image data. +/// internal static class ExrCompressorFactory { + /// + /// Creates the specified exr data compressor. + /// + /// The compression method. + /// The memory allocator. + /// The output stream. + /// The bytes per block. + /// The bytes per row. + /// The deflate compression level. + /// A compressor for EXR image data. public static ExrBaseCompressor Create( ExrCompression method, MemoryAllocator allocator, Stream output, uint bytesPerBlock, uint bytesPerRow, - DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) - { - switch (method) + DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch { - case ExrCompression.None: - return new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow); - case ExrCompression.Zips: - return new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel); - case ExrCompression.Zip: - return new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel); - - default: - throw ExrThrowHelper.NotSupportedCompressor(method.ToString()); - } - } + ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow), + ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), + ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), + _ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()), + }; } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 2696a289c..62519666b 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -7,8 +7,22 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression; +/// +/// The Factory class for creating a EXR data decompressor. +/// internal static class ExrDecompressorFactory { + /// + /// Creates a decomprssor for a specific EXR compression type. + /// + /// The compression method. + /// The memory allocator. + /// The width in pixels of the image. + /// The bytes per block. + /// The bytes per row. + /// The rows per block. + /// The number of image channels. + /// Decompressor for EXR image data. public static ExrBaseDecompressor Create( ExrCompression method, MemoryAllocator memoryAllocator, @@ -16,22 +30,13 @@ internal static class ExrDecompressorFactory uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, - int channelCount) - { - switch (method) + int channelCount) => method switch { - case ExrCompression.None: - return new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); - case ExrCompression.Zips: - return new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); - case ExrCompression.Zip: - return new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); - case ExrCompression.RunLengthEncoded: - return new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow); - case ExrCompression.B44: - return new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount); - default: - throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)); - } - } + ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), + ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), + ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), + ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), + ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), + _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), + }; } diff --git a/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs b/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs index beeabe35e..9e2cd009f 100644 --- a/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrImageType.cs @@ -3,9 +3,19 @@ namespace SixLabors.ImageSharp.Formats.Exr.Constants; +/// +/// Enum for the differnt exr image type. +/// internal enum ExrImageType { + /// + /// The image data is stored in scan lines. + /// ScanLine = 0, + /// + /// The image data is stored in tile. + /// This is not yet supported. + /// Tiled = 1 } diff --git a/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs b/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs index 56573472c..73e86d6ef 100644 --- a/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs +++ b/src/ImageSharp/Formats/Exr/Constants/ExrLineOrder.cs @@ -3,11 +3,23 @@ namespace SixLabors.ImageSharp.Formats.Exr.Constants; +/// +/// Enum for the different scan line ordering. +/// internal enum ExrLineOrder : byte { + /// + /// The scan lines are written from top-to-bottom. + /// IncreasingY = 0, + /// + /// The scan lines are written from bottom-to-top. + /// DecreasingY = 1, + /// + /// The Scan lines are written in no particular oder. + /// RandomY = 2 } diff --git a/src/ImageSharp/Formats/Exr/ExrAttribute.cs b/src/ImageSharp/Formats/Exr/ExrAttribute.cs index b4e95f1d4..093c97391 100644 --- a/src/ImageSharp/Formats/Exr/ExrAttribute.cs +++ b/src/ImageSharp/Formats/Exr/ExrAttribute.cs @@ -5,11 +5,20 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.Exr; +/// +/// Repressents an exr image attribute. +/// [DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] internal class ExrAttribute { public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); + /// + /// Initializes a new instance of the class. + /// + /// The name of the attribute. + /// The type of the attribute. + /// The length in bytes. public ExrAttribute(string name, string type, int length) { this.Name = name; @@ -17,9 +26,18 @@ internal class ExrAttribute this.Length = length; } + /// + /// Gets the name of the attribute. + /// public string Name { get; } + /// + /// Gets the type of the attribute. + /// public string Type { get; } + /// + /// Gets the length in bytes of the attribute. + /// public int Length { get; } } diff --git a/src/ImageSharp/Formats/Exr/ExrBox2i.cs b/src/ImageSharp/Formats/Exr/ExrBox2i.cs index 032e60d92..c9c53d648 100644 --- a/src/ImageSharp/Formats/Exr/ExrBox2i.cs +++ b/src/ImageSharp/Formats/Exr/ExrBox2i.cs @@ -5,9 +5,19 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.Exr; +/// +/// Integer region definition. +/// [DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] internal readonly struct ExrBox2i { + /// + /// Initializes a new instance of the struct. + /// + /// The minimum x value. + /// The minimum y value. + /// The maximum x value. + /// The maximum y value. public ExrBox2i(int xMin, int yMin, int xMax, int yMax) { this.XMin = xMin; @@ -16,11 +26,23 @@ internal readonly struct ExrBox2i this.YMax = yMax; } + /// + /// Gets the minimum x value. + /// public int XMin { get; } + /// + /// Gets the minimum y value. + /// public int YMin { get; } + /// + /// Gets the maximum x value. + /// public int XMax { get; } + /// + /// Gets the maximum y value. + /// public int YMax { get; } } diff --git a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs index d4f5825b9..5aae64f28 100644 --- a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs @@ -7,26 +7,55 @@ using SixLabors.ImageSharp.Formats.Exr.Constants; namespace SixLabors.ImageSharp.Formats.Exr; +/// +/// Information about a pixel channel. +/// [DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] [StructLayout(LayoutKind.Sequential, Pack = 1)] internal readonly struct ExrChannelInfo { - public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte pLinear, int xSampling, int ySampling) + /// + /// Initializes a new instance of the struct. + /// + /// Name of the channel. + /// The type of the pixel data. + /// Linear flag, possible values are 0 and 1. + /// X sampling. + /// Y sampling. + public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte linear, int xSampling, int ySampling) { this.ChannelName = channelName; this.PixelType = pixelType; - this.PLinear = pLinear; + this.Linear = linear; this.XSampling = xSampling; this.YSampling = ySampling; } + /// + /// Gets the channel name. + /// public string ChannelName { get; } + + /// + /// Gets the type of the pixel data. + /// public ExrPixelType PixelType { get; } - public byte PLinear { get; } + /// + /// Gets the linear flag. Hint to lossy compression methods that indicates whether + /// human perception of the quantity represented by this channel + /// is closer to linear or closer to logarithmic. + /// + public byte Linear { get; } + /// + /// Gets the x sampling value. + /// public int XSampling { get; } + /// + /// Gets the y sampling value. + /// public int YSampling { get; } } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index a4fa4b0ac..0b75154c2 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -120,6 +120,21 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return image; } + /// + protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + ExrHeaderAttributes header = this.ReadExrHeader(stream); + + return new ImageInfo(new Size(header.DataWindow.XMax, header.DataWindow.YMax), this.metadata); + } + + /// + /// Decodes image data with floating point pixel data. + /// + /// The type of the pixels. + /// The stream to read from. + /// The pixel buffer. + /// The cancellation token. private void DecodeFloatingPointPixelData(BufferedReadStream stream, Buffer2D pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -178,6 +193,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } } + /// + /// Decodes image data with unsigned int pixel data. + /// + /// The type of the pixels. + /// The stream to read from. + /// The pixel buffer. + /// The cancellation token. private void DecodeUnsignedIntPixelData(BufferedReadStream stream, Buffer2D pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -236,6 +258,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } } + /// + /// Reads float image channel data. + /// + /// The stream to read from. + /// The channel info. + /// The decompressed pixel data. + /// The red channel pixel data. + /// The green channel pixel data. + /// The blue channel pixel data. + /// The alpha channel pixel data. + /// The width of a row in pixels. + /// The bytes read. private static int ReadFloatChannelData( BufferedReadStream stream, ExrChannelInfo channel, @@ -275,6 +309,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } } + /// + /// Reads UINT image channel data. + /// + /// The stream to read from. + /// The channel info. + /// The decompressed pixel data. + /// The red channel pixel data. + /// The green channel pixel data. + /// The blue channel pixel data. + /// The alpha channel pixel data. + /// The width of a row in pixels. + /// The bytes read. private int ReadUnsignedIntChannelData( BufferedReadStream stream, ExrChannelInfo channel, @@ -313,6 +359,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } } + /// + /// Reads the channel data for pixel type HALF or FLOAT. + /// + /// The channel info. + /// The decompressed pixel data. + /// The pixel data as float. + /// The width in pixel of a row. + /// The bytes read. private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch { ExrPixelType.Half => ReadPixelRowChannelHalfSingle(decompressedPixelData, pixelData, width), @@ -320,12 +374,27 @@ internal sealed class ExrDecoderCore : ImageDecoderCore _ => 0, }; + /// + /// Reads the channel data for pixel type UINT. + /// + /// The channel info. + /// The decompressed pixel data. + /// The pixel data as uint. + /// The width in pixels. + /// The bytes read. private static int ReadChannelData(ExrChannelInfo channel, Span decompressedPixelData, Span pixelData, int width) => channel.PixelType switch { ExrPixelType.UnsignedInt => ReadPixelRowChannelUnsignedInt(decompressedPixelData, pixelData, width), _ => 0, }; + /// + /// Reads a pixel row with the pixel data being 16 bit half values. + /// + /// The decompressed pixel data. + /// The channel data as float. + /// The width of a row in pixels. + /// The bytes read. private static int ReadPixelRowChannelHalfSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; @@ -339,6 +408,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return offset; } + /// + /// Reads a pixel row with 32 bit float pixel data. + /// + /// The decompressed pixel data. + /// The pixel data as float. + /// The width in pixels of a row. + /// The bytes read. private static int ReadPixelRowChannelSingle(Span decompressedPixelData, Span channelData, int width) { int offset = 0; @@ -352,6 +428,13 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return offset; } + /// + /// Reads a pixel row with the pixel typ UINT. + /// + /// The decompressed pixel bytes. + /// The uint pixel data. + /// The width of a row in pixels. + /// The bytes read. private static int ReadPixelRowChannelUnsignedInt(Span decompressedPixelData, Span channelData, int width) { int offset = 0; @@ -364,14 +447,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return offset; } - /// - protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - ExrHeaderAttributes header = this.ReadExrHeader(stream); - - return new ImageInfo(new Size(header.DataWindow.XMax, header.DataWindow.YMax), this.metadata); - } - + /// + /// Validates that all image channels have the same type and are among the supported pixel types. + /// + /// The pixel type. private ExrPixelType ValidateChannels() { if (this.Channels.Count == 0) @@ -380,12 +459,42 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } // Find pixel the type of any channel which is R, G, B or A. - ExrPixelType pixelType = this.FindPixelType(); + ExrPixelType? pixelType = null; + for (int i = 0; i < this.Channels.Count; i++) + { + if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha, StringComparison.Ordinal) || + this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance, StringComparison.Ordinal)) + { + if (!pixelType.HasValue) + { + pixelType = this.Channels[i].PixelType; + } + else + { + if (pixelType != this.Channels[i].PixelType) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); + } + } + } + } - return pixelType; + if (!pixelType.HasValue) + { + ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); + } + + return pixelType.Value; } - private ExrImageDataType ReadImageDataType() + /// + /// Determines the type image from the channel information. + /// + /// The image data type. + private ExrImageDataType DetermineImageDataType() { bool hasRedChannel = false; bool hasGreenChannel = false; @@ -438,6 +547,12 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return ExrImageDataType.Unknown; } + /// + /// Reads the exr image header. + /// + /// + /// The stream. + /// The image header attributes. private ExrHeaderAttributes ReadExrHeader(BufferedReadStream stream) { // Skip over the magick bytes, we already know its an EXR image. @@ -471,7 +586,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Channels = this.HeaderAttributes.Channels; this.Compression = this.HeaderAttributes.Compression; this.PixelType = this.ValidateChannels(); - this.ImageDataType = this.ReadImageDataType(); + this.ImageDataType = this.DetermineImageDataType(); this.metadata = new ImageMetadata(); @@ -483,6 +598,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return this.HeaderAttributes; } + /// + /// Parses the image header attributes. + /// + /// The stream to read from. + /// The image header attributes. private ExrHeaderAttributes ParseHeaderAttributes(BufferedReadStream stream) { ExrAttribute attribute = this.ReadAttribute(stream); @@ -599,6 +719,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return header; } + /// + /// Reads a attrbute from the stream, which consist of a name, a type and a size in bytes. + /// + /// The stream to read from. + /// A attribute. private ExrAttribute ReadAttribute(BufferedReadStream stream) { string attributeName = ReadString(stream); @@ -608,12 +733,16 @@ internal sealed class ExrDecoderCore : ImageDecoderCore } string attributeType = ReadString(stream); - int attributeSize = this.ReadSignedInteger(stream); return new ExrAttribute(attributeName, attributeType, attributeSize); } + /// + /// Reads a box attribute, which is a xMin, xMax and yMin, yMax value. + /// + /// The stream to reaad from. + /// A box struct. private ExrBox2i ReadBoxInteger(BufferedReadStream stream) { int xMin = this.ReadSignedInteger(stream); @@ -624,6 +753,12 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return new ExrBox2i(xMin, yMin, xMax, yMax); } + /// + /// Reads the channel list from the stream. + /// + /// The stream to read from. + /// The size in bytes of the channel list attribute. + /// The channel list. private List ReadChannelList(BufferedReadStream stream, int attributeSize) { List channels = []; @@ -637,12 +772,18 @@ internal sealed class ExrDecoderCore : ImageDecoderCore // Last byte should be a null byte. if (stream.ReadByte() == -1) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read exr channel list!"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data to read the exr channel list!"); } return channels; } + /// + /// Reads the channel information from the stream. + /// + /// The stream to read from. + /// The bytes read. + /// Channel info. private ExrChannelInfo ReadChannelInfo(BufferedReadStream stream, out int bytesRead) { string channelName = ReadString(stream); @@ -670,6 +811,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return new ExrChannelInfo(channelName, pixelType, pLinear, xSampling, ySampling); } + /// + /// Reads a the string from the stream. + /// + /// The stream to read from. + /// A string. private static string ReadString(BufferedReadStream stream) { StringBuilder str = new(); @@ -694,45 +840,20 @@ internal sealed class ExrDecoderCore : ImageDecoderCore return str.ToString(); } - private ExrPixelType FindPixelType() - { - ExrPixelType? pixelType = null; - for (int i = 0; i < this.Channels.Count; i++) - { - if (this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Blue, StringComparison.Ordinal) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Green, StringComparison.Ordinal) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Red, StringComparison.Ordinal) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Alpha, StringComparison.Ordinal) || - this.Channels[i].ChannelName.Equals(ExrConstants.ChannelNames.Luminance, StringComparison.Ordinal)) - { - if (!pixelType.HasValue) - { - pixelType = this.Channels[i].PixelType; - } - else - { - if (pixelType != this.Channels[i].PixelType) - { - ExrThrowHelper.ThrowNotSupported("Pixel channel data is expected to be the same for all channels."); - } - } - } - } - - if (!pixelType.HasValue) - { - ExrThrowHelper.ThrowNotSupported("Pixel channel data is unknown! Only R, G, B, A and Y are supported."); - } - - return pixelType.Value; - } - + /// + /// Determines whether the compression is supported. + /// + /// True if the compression is supported; otherwise, false>. private bool IsSupportedCompression() => this.Compression switch { ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true, _ => false, }; + /// + /// Determines whether this image has alpha channel. + /// + /// True if this image has a alpha channel; otherwise, false. private bool HasAlpha() { foreach (ExrChannelInfo channelInfo in this.Channels) diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 9f86e79e6..56584132b 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -147,6 +147,18 @@ internal sealed class ExrEncoderCore } } + /// + /// Encodes and writes pixel data with float pixel data to the stream. + /// + /// The type of the pixels. + /// The stream to write to. + /// The pixel bufer. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The imagechannels. + /// The compression to use. + /// The cancellation token. + /// The array of pixel row offsets. private ulong[] EncodeFloatingPointPixelData( Stream stream, Buffer2D pixels, @@ -227,6 +239,18 @@ internal sealed class ExrEncoderCore return rowOffsets; } + /// + /// Encodes and writes pixel data with the unsigned int pixel type to the stream. + /// + /// The type of the pixels. + /// The stream to write to. + /// The pixel bufer. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The imagechannels. + /// The compression to use. + /// The cancellation token. + /// The array of pixel row offsets. private ulong[] EncodeUnsignedIntPixelData( Stream stream, Buffer2D pixels, @@ -301,6 +325,11 @@ internal sealed class ExrEncoderCore return rowOffsets; } + /// + /// Writes the image header to the stream. + /// + /// The stream to write to. + /// The header. private void WriteHeader(Stream stream, ExrHeaderAttributes header) { this.WriteChannels(stream, header.Channels); @@ -314,6 +343,15 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } + /// + /// Writes a row of pixels with the FLOAT pixel type to a buffer. + /// + /// The buffer to write to. + /// The width of a row in pixels. + /// The alpha channel buffer. + /// The blue channel buffer. + /// The green channel buffer. + /// The red channel buffer. private static void WriteSingleRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; @@ -342,6 +380,15 @@ internal sealed class ExrEncoderCore } } + /// + /// Writes a row of pixels with the HALF pixel type to a buffer. + /// + /// The buffer to write to. + /// The width of a row in pixels. + /// The alpha channel buffer. + /// The blue channel buffer. + /// The green channel buffer. + /// The red channel buffer. private static void WriteHalfSingleRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; @@ -370,6 +417,15 @@ internal sealed class ExrEncoderCore } } + /// + /// Writes a row of pixels with unsigned int pixel data to a buffer. + /// + /// The buffer to write to. + /// The width of the row in pixels. + /// The alpha channel buffer. + /// The blue channel buffer. + /// The green channel buffer. + /// The red channel buffer. private static void WriteUnsignedIntRow(Span buffer, int width, Span alphaBuffer, Span blueBuffer, Span greenBuffer, Span redBuffer) { int offset = 0; @@ -398,6 +454,12 @@ internal sealed class ExrEncoderCore } } + /// + /// Writes the row offsets to the stream. + /// + /// The stream to write to. + /// The height in pixels of the image. + /// The row offsets. private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets) { for (int i = 0; i < height; i++) @@ -407,6 +469,11 @@ internal sealed class ExrEncoderCore } } + /// + /// Writes the channel infos to the stream. + /// + /// The stream to write to. + /// The channels. private void WriteChannels(Stream stream, IList channels) { int attributeSize = 0; @@ -429,24 +496,70 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } + /// + /// Writes info about a single channel to the stream. + /// + /// The stream to write to. + /// The channel information. + private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) + { + WriteString(stream, channelInfo.ChannelName); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); + stream.Write(this.buffer.AsSpan(0, 4)); + + stream.WriteByte(channelInfo.Linear); + + // Next 3 bytes are reserved and will set to zero. + stream.WriteByte(0); + stream.WriteByte(0); + stream.WriteByte(0); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + + BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); + stream.Write(this.buffer.AsSpan(0, 4)); + } + + /// + /// Writes the compression type to the stream. + /// + /// The stream to write to. + /// The compression type. private void WriteCompression(Stream stream, ExrCompression compression) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); stream.WriteByte((byte)compression); } + /// + /// Writes the pixel aspect ratio to the stream. + /// + /// The stream to write to. + /// The aspect ratio. private void WritePixelAspectRatio(Stream stream, float aspectRatio) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, aspectRatio); } + /// + /// Writes the line order to the stream. + /// + /// The stream to write to. + /// The line order. private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); stream.WriteByte((byte)lineOrder); } + /// + /// Writes the screen window center to the stream. + /// + /// The stream to write to. + /// The screen window center. private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); @@ -454,24 +567,46 @@ internal sealed class ExrEncoderCore this.WriteSingle(stream, screenWindowCenter.Y); } + /// + /// Writes the screen width to the stream. + /// + /// The stream to write to. + /// Width of the screen window. private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); this.WriteSingle(stream, screenWindowWidth); } + /// + /// Writes the data window to the stream. + /// + /// The stream to write to. + /// The data window. private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); this.WriteBoxInteger(stream, dataWindow); } + /// + /// Writes the display window to the stream. + /// + /// The stream to write to. + /// The display window. private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) { this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); this.WriteBoxInteger(stream, displayWindow); } + /// + /// Writes attribute information to the stream. + /// + /// The stream to write to. + /// The name of the attribute. + /// The type of the attribute. + /// The size in bytes of the attribute. private void WriteAttributeInformation(Stream stream, string name, string type, int size) { // Write attribute name. @@ -485,27 +620,11 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 4)); } - private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) - { - WriteString(stream, channelInfo.ChannelName); - - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); - stream.Write(this.buffer.AsSpan(0, 4)); - - stream.WriteByte(channelInfo.PLinear); - - // Next 3 bytes are reserved and will set to zero. - stream.WriteByte(0); - stream.WriteByte(0); - stream.WriteByte(0); - - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); - stream.Write(this.buffer.AsSpan(0, 4)); - - BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); - stream.Write(this.buffer.AsSpan(0, 4)); - } - + /// + /// Writes a string to the stream. + /// + /// The stream to write to. + /// The string to write. private static void WriteString(Stream stream, string str) { foreach (char c in str) @@ -517,6 +636,11 @@ internal sealed class ExrEncoderCore stream.WriteByte(0); } + /// + /// Writes box struct with xmin, xmax, ymin and y max to the stream. + /// + /// The stream to write to. + /// The box to write. private void WriteBoxInteger(Stream stream, ExrBox2i box) { BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); @@ -532,6 +656,11 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 4)); } + /// + /// Writes 32 bit float value to the stream. + /// + /// The stream to write to. + /// The float value to write. [MethodImpl(InliningOptions.ShortMethod)] private unsafe void WriteSingle(Stream stream, float value) { @@ -539,9 +668,19 @@ internal sealed class ExrEncoderCore stream.Write(this.buffer.AsSpan(0, 4)); } + /// + /// Writes a 32 bit float value to a buffer. + /// + /// The buffer to write to. + /// The float value to write. [MethodImpl(InliningOptions.ShortMethod)] private static unsafe void WriteSingleToBuffer(Span buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); + /// + /// Writes a 16 bit float value to a buffer. + /// + /// The buffer to write to. + /// The float value to write. [MethodImpl(InliningOptions.ShortMethod)] private static void WriteHalfSingleToBuffer(Span buffer, float value) { @@ -549,6 +688,11 @@ internal sealed class ExrEncoderCore BinaryPrimitives.WriteUInt16LittleEndian(buffer, valueAsShort); } + /// + /// Writes one unsigned int to a buffer. + /// + /// The buffer to write to. + /// The uint value to write. [MethodImpl(InliningOptions.ShortMethod)] private static void WriteUnsignedIntToBuffer(Span buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); } diff --git a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs index cdcddd117..9a6ce7056 100644 --- a/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs +++ b/src/ImageSharp/Formats/Exr/ExrHeaderAttributes.cs @@ -11,6 +11,20 @@ namespace SixLabors.ImageSharp.Formats.Exr; /// internal class ExrHeaderAttributes { + /// + /// Initializes a new instance of the class. + /// + /// The image channels. + /// The compression used. + /// The data window. + /// The display window. + /// The line order. + /// The aspect ratio. + /// Width of the screen window. + /// The screen window center. + /// Size of the tile in x dimension. + /// Size of the tile in y dimension. + /// The chunk count. public ExrHeaderAttributes( IList channels, ExrCompression compression, From ce545b7892f8ad7125c5a4b059c28f809acf9183 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 Apr 2026 18:40:50 +0200 Subject: [PATCH 143/240] Fix stylecop complains --- src/ImageSharp/Formats/Exr/ExrChannelInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs index 5aae64f28..f9269f786 100644 --- a/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs +++ b/src/ImageSharp/Formats/Exr/ExrChannelInfo.cs @@ -36,7 +36,6 @@ internal readonly struct ExrChannelInfo /// public string ChannelName { get; } - /// /// Gets the type of the pixel data. /// From d98ffac34c92392370047538eda6a24ea25e9b90 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 Apr 2026 19:19:04 +0200 Subject: [PATCH 144/240] Add test for setting pixel type for encoding exr images --- .../Formats/Exr/ExrEncoderTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs index 6aa3ebc70..2f507ea51 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs @@ -16,6 +16,28 @@ public class ExrEncoderTests { protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(ExrFormat.Instance); + [Theory] + [InlineData(null, ExrPixelType.Half)] + [InlineData(ExrPixelType.Float, ExrPixelType.Float)] + [InlineData(ExrPixelType.Half, ExrPixelType.Half)] + [InlineData(ExrPixelType.UnsignedInt, ExrPixelType.UnsignedInt)] + public void EncoderOptions_SetPixelType_Works(ExrPixelType? pixelType, ExrPixelType? expectedPixelType) + { + // arrange + ExrEncoder exrEncoder = new() { PixelType = pixelType }; + using Image input = new Image(10, 10); + using MemoryStream memStream = new(); + + // act + input.Save(memStream, exrEncoder); + + // assert + memStream.Position = 0; + using Image output = Image.Load(memStream); + ExrMetadata exrMetaData = output.Metadata.GetExrMetadata(); + Assert.Equal(expectedPixelType, exrMetaData.PixelType); + } + [Theory] [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] public void ExrEncoder_WithNoCompression_Works(TestImageProvider provider) From 00db2e9256bbd17c606a5cc182121f99a3303307 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 Apr 2026 19:19:48 +0200 Subject: [PATCH 145/240] Actually use pixel type from encoder in ExrEncoderCore constructor --- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 56584132b..04ca5e40c 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -55,6 +55,7 @@ internal sealed class ExrEncoderCore this.encoder = encoder; this.memoryAllocator = memoryAllocator; this.Compression = encoder.Compression ?? ExrCompression.None; + this.pixelType = encoder.PixelType; } /// From 03670ab0322d3afe091faa0253dc761c39d4c6a0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 11:24:08 +1000 Subject: [PATCH 146/240] Revert some convolution processors --- .../Convolution2PassProcessor{TPixel}.cs | 26 ++++++------------- .../ConvolutionProcessor{TPixel}.cs | 15 +++-------- .../EdgeDetectorCompassProcessor{TPixel}.cs | 12 +++------ 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 6d1d7c23c..38b6cab3f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -123,15 +123,10 @@ internal class Convolution2PassProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - using (IMemoryOwner hBuffer = allocator.Allocate(horizontalOperation.GetRequiredBufferLength(interest))) - { - Span hSpan = hBuffer.Memory.Span; - - for (int y = interest.Top; y < interest.Bottom; y++) - { - horizontalOperation.Invoke(y, hSpan); - } - } + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in horizontalOperation); // Vertical convolution VerticalConvolutionRowOperation verticalOperation = new( @@ -143,15 +138,10 @@ internal class Convolution2PassProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - using (IMemoryOwner vBuffer = allocator.Allocate(verticalOperation.GetRequiredBufferLength(interest))) - { - Span vSpan = vBuffer.Memory.Span; - - for (int y = interest.Top; y < interest.Bottom; y++) - { - verticalOperation.Invoke(y, vSpan); - } - } + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in verticalOperation); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index b704f556f..69d72b3cc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -97,17 +97,10 @@ internal class ConvolutionProcessor : ImageProcessor map.BuildSamplingOffsetMap(this.KernelXY.Rows, this.KernelXY.Columns, interest, this.BorderWrapModeX, this.BorderWrapModeY); RowOperation operation = new(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); - - // Convolution is memory-bandwidth-bound with low arithmetic intensity. - // Parallelization degrades performance due to cache line contention from - // overlapping source row reads. See #3111. - using IMemoryOwner buffer = allocator.Allocate(operation.GetRequiredBufferLength(interest)); - Span span = buffer.Memory.Span; - - for (int y = interest.Top; y < interest.Bottom; y++) - { - operation.Invoke(y, span); - } + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index f1edb75a2..eae748166 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -83,15 +83,11 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor processor.Apply(pass); } - // Convolution is memory-bandwidth-bound with low arithmetic intensity. - // Parallelization degrades performance due to cache line contention from - // overlapping source row reads. See #3111. RowOperation operation = new(source.PixelBuffer, pass.PixelBuffer, interest); - - for (int y = interest.Top; y < interest.Bottom; y++) - { - operation.Invoke(y); - } + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); } } From 3a879338c6bd052fab73bb1594fba81c67215318 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 11:26:02 +1000 Subject: [PATCH 147/240] Cleanup --- .../Convolution/Convolution2PassProcessor{TPixel}.cs | 10 +++------- .../Convolution/ConvolutionProcessor{TPixel}.cs | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 38b6cab3f..15ea98936 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -109,10 +109,6 @@ internal class Convolution2PassProcessor : ImageProcessor MemoryAllocator allocator = this.Configuration.MemoryAllocator; - // Convolution is memory-bandwidth-bound with low arithmetic intensity. - // Parallelization degrades performance due to cache line contention from - // overlapping source row reads. See #3111. - // Horizontal convolution HorizontalConvolutionRowOperation horizontalOperation = new( interest, @@ -124,9 +120,9 @@ internal class Convolution2PassProcessor : ImageProcessor this.PreserveAlpha); ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in horizontalOperation); + this.Configuration, + interest, + in horizontalOperation); // Vertical convolution VerticalConvolutionRowOperation verticalOperation = new( diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 69d72b3cc..feaaf30ce 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; From 988314aae755ef2ce0e23ede5d1dbf86713a3a6e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 16 Apr 2026 03:28:53 +0200 Subject: [PATCH 148/240] Add more throughput benchmarks and pass Configuration where missed (#3114) * Add GaussianBlur throughput benchmark and pass Configuration where missed * also add `-m EdgesCompass` --- .../ProcessorThroughputBenchmark.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs index e9adf5844..e85a3ee97 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs @@ -6,6 +6,7 @@ using CommandLine; using CommandLine.Text; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; @@ -49,10 +50,12 @@ public sealed class ProcessorThroughputBenchmark { Method.Crop => this.Crop, Method.Edges => this.DetectEdges, + Method.EdgesCompass => this.EdgesCompass, Method.DrawImage => this.DrawImage, Method.BinaryThreshold => this.BinaryThreshold, Method.Histogram => this.Histogram, Method.OilPaint => this.OilPaint, + Method.GaussianBlur => this.GaussianBlur, _ => throw new NotImplementedException(), }; @@ -138,6 +141,13 @@ public sealed class ProcessorThroughputBenchmark return image.Width * image.Height; } + private int EdgesCompass() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.DetectEdges(EdgeDetectorCompassKernel.Kirsch)); + return image.Width * image.Height; + } + private int Crop() { using Image image = new(this.options.Width, this.options.Height); @@ -151,32 +161,41 @@ public sealed class ProcessorThroughputBenchmark { using Image image = new(this.options.Width, this.options.Height); using Image foreground = new(this.options.Width, this.options.Height); - image.Mutate(c => c.DrawImage(foreground, 0.5f)); + image.Mutate(this.configuration, c => c.DrawImage(foreground, 0.5f)); return image.Width * image.Height; } private int BinaryThreshold() { using Image image = new(this.options.Width, this.options.Height); - image.Mutate(c => c.BinaryThreshold(0.5f)); + image.Mutate(this.configuration, c => c.BinaryThreshold(0.5f)); return image.Width * image.Height; } private int Histogram() { using Image image = new(this.options.Width, this.options.Height); - image.Mutate(c => c.HistogramEqualization()); + image.Mutate(this.configuration, c => c.HistogramEqualization()); + return image.Width * image.Height; + } + + private int GaussianBlur() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, c => c.GaussianBlur()); return image.Width * image.Height; } private enum Method { Edges, + EdgesCompass, Crop, DrawImage, BinaryThreshold, Histogram, - OilPaint + OilPaint, + GaussianBlur, } private sealed class CommandLineOptions From 0390442feedad89ebb5a66d26223cfb5a8816279 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 18:14:31 +1000 Subject: [PATCH 149/240] Fix ColorNumerics and FromVector4 --- .../Common/Helpers/ColorNumerics.cs | 42 +++++++++++++++++-- .../PixelImplementations/Rgb96.cs | 14 +++++-- .../PixelImplementations/Rgba128.cs | 15 +++++-- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 735f6bc59..066dbec3d 100644 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs @@ -97,7 +97,7 @@ internal static class ColorNumerics /// Scales a value from a 16 bit to an /// 8 bit equivalent. /// - /// The 8 bit component value. + /// The 16 bit component value. /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte From16BitTo8Bit(ushort component) => @@ -133,13 +133,47 @@ internal static class ColorNumerics (byte)(((component * 255) + 32895) >> 16); /// - /// Scales a value from an 32 bit to - /// an 8 bit equivalent. + /// Scales a value from a 32 bit to an + /// 8 bit equivalent. /// /// The 32 bit component value. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte From32BitTo8Bit(uint component) => (byte)(component >> 24); + public static byte From32BitTo8Bit(uint component) => + + // To scale to 8 bits from a 32-bit value V the required value is: + // + // (V * 255) / 4294967295 + // + // Since: + // + // 4294967295 = 255 * 16843009 + // + // this reduces exactly to: + // + // V / 16843009 + // + // To round to nearest using integer arithmetic we add half the divisor + // before dividing: + // + // (V + 16843009 / 2) / 16843009 + // + // where: + // + // 16843009 / 2 = 8421504.5 + // + // Using 8421504 ensures correct round-to-nearest behaviour: + // + // 8421504 -> 0 + // 8421505 -> 1 + // + // The addition must be performed in 64-bit to avoid overflow for large + // input values (for example uint.MaxValue). + // + // Final exact integer implementation: + // + // ((ulong)V + 8421504) / 16843009 + (byte)((component + 8421504UL) / 16843009UL); /// /// Scales a value from an 8 bit to diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs index 5f61d5778..12275061d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -19,7 +19,12 @@ public partial struct Rgb96 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; - private const float Max = uint.MaxValue; + // Use double here because at this magnitude a float cannot represent all 32-bit + // integer values exactly. A float only has 24 bits of precision, so around + // uint.MaxValue it can only represent multiples of 256 and will round + // 4294967295 up to 4294967296. Double has 53 bits of precision and can + // represent all uint values exactly, avoiding precision loss before scaling. + private const double Max = uint.MaxValue; /// /// Gets the red component. @@ -96,8 +101,11 @@ public partial struct Rgb96 : IPixel, IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb96 FromVector4(Vector4 source) { - source = Numerics.Clamp(source, Vector4.Zero, Vector4.One) * Max; - return new Rgb96((uint)MathF.Round(source.X), (uint)MathF.Round(source.Y), (uint)MathF.Round(source.Z)); + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new Rgb96( + (uint)Math.Round(source.X * Max), + (uint)Math.Round(source.Y * Max), + (uint)Math.Round(source.Z * Max)); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs index 77934eac8..7fc62b7ff 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -19,7 +19,12 @@ public partial struct Rgba128 : IPixel, IEquatable { private const float InvMax = 1.0f / uint.MaxValue; - private const float Max = uint.MaxValue; + // Use double here because at this magnitude a float cannot represent all 32-bit + // integer values exactly. A float only has 24 bits of precision, so around + // uint.MaxValue it can only represent multiples of 256 and will round + // 4294967295 up to 4294967296. Double has 53 bits of precision and can + // represent all uint values exactly, avoiding precision loss before scaling. + private const double Max = uint.MaxValue; /// /// Gets the red component. @@ -96,8 +101,12 @@ public partial struct Rgba128 : IPixel, IEquatable /// public static Rgba128 FromVector4(Vector4 source) { - source = Numerics.Clamp(source, Vector4.Zero, Vector4.One) * Max; - return new Rgba128((uint)MathF.Round(source.X), (uint)MathF.Round(source.Y), (uint)MathF.Round(source.Z), (uint)MathF.Round(source.W)); + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new Rgba128( + (uint)Math.Round(source.X * Max), + (uint)Math.Round(source.Y * Max), + (uint)Math.Round(source.Z * Max), + (uint)Math.Round(source.W * Max)); } /// From 6b486e30f1fad326b60879642568d195d302bea7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 18:36:33 +1000 Subject: [PATCH 150/240] Fix failing conversion tests --- tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs | 2 +- tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs index 7e6b8f55f..e70e2fa0f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs @@ -75,7 +75,7 @@ public class Rgb96Tests public void Rgb96_ToRgba32() { // arrange - Rgb96 rgb96 = new((uint)(uint.MaxValue * 0.1f), uint.MaxValue / 2, uint.MaxValue); + Rgb96 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue); Rgba32 expected = new(25, 127, 255, 255); // act diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs index f00367187..339d57ed4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs @@ -76,7 +76,7 @@ public class Rgba128Tests public void Rgba128_ToRgba32() { // arrange - Rgba128 rgb96 = new((uint)(uint.MaxValue * 0.1f), uint.MaxValue / 2, uint.MaxValue, uint.MaxValue); + Rgba128 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue, uint.MaxValue); Rgba32 expected = new(25, 127, 255, 255); // act From f96d28714deca4e005c9f1c87901d783883ad1c0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 18:47:07 +1000 Subject: [PATCH 151/240] Update ColorNumericsTests.cs --- .../Helpers/ColorNumericsTests.cs | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs index 1bf137f34..4e24b111b 100644 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs @@ -24,5 +24,59 @@ public class ColorNumericsTests Assert.Equal(expected, actual); } - // TODO: We need to test all ColorNumerics methods! + [Theory] + [InlineData((ushort)0, (byte)0)] + [InlineData((ushort)128, (byte)0)] + [InlineData((ushort)129, (byte)1)] + [InlineData((ushort)257, (byte)1)] + [InlineData((ushort)32896, (byte)128)] + [InlineData(ushort.MaxValue, byte.MaxValue)] + public void From16BitTo8Bit_ReturnsExpectedValue(ushort component, byte expected) + { + byte actual = ColorNumerics.From16BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + + [Fact] + public void From16BitTo8Bit_RoundTripsAllExpanded8BitValues() + { + for (int i = 0; i <= byte.MaxValue; i++) + { + byte expected = (byte)i; + ushort component = ColorNumerics.From8BitTo16Bit(expected); + + byte actual = ColorNumerics.From16BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + } + + [Theory] + [InlineData(0U, (byte)0)] + [InlineData(8421504U, (byte)0)] + [InlineData(8421505U, (byte)1)] + [InlineData(16843009U, (byte)1)] + [InlineData(2155905152U, (byte)128)] + [InlineData(uint.MaxValue, byte.MaxValue)] + public void From32BitTo8Bit_ReturnsExpectedValue(uint component, byte expected) + { + byte actual = ColorNumerics.From32BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + + [Fact] + public void From32BitTo8Bit_RoundTripsAllExpanded8BitValues() + { + for (int i = 0; i <= byte.MaxValue; i++) + { + byte expected = (byte)i; + uint component = ColorNumerics.From8BitTo32Bit(expected); + + byte actual = ColorNumerics.From32BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + } } From dd395e1c03f2c708eb19dd9852ca4aeff7ebc39d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 19:27:10 +1000 Subject: [PATCH 152/240] Enhance metadata conversion --- src/ImageSharp/Formats/Exr/ExrMetadata.cs | 48 +++++++++++++++++-- .../Formats/Exr/ExrMetadataTests.cs | 30 ++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/ExrMetadata.cs b/src/ImageSharp/Formats/Exr/ExrMetadata.cs index 1fa724657..22762289c 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -95,14 +95,52 @@ public class ExrMetadata : IFormatMetadata } /// - public FormatConnectingMetadata ToFormatConnectingMetadata() => new() + public FormatConnectingMetadata ToFormatConnectingMetadata() { - EncodingType = this.Compression is ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Pxr24 ? EncodingType.Lossy : EncodingType.Lossless, - PixelTypeInfo = this.GetPixelTypeInfo() - }; + EncodingType type = this.Compression is ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Pxr24 + ? EncodingType.Lossy + : EncodingType.Lossless; + + return new() + { + EncodingType = type, + PixelTypeInfo = this.GetPixelTypeInfo() + }; + } /// - public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => new() { PixelType = ExrPixelType.Half }; + public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) + { + PixelTypeInfo pixelTypeInfo = metadata.PixelTypeInfo; + PixelComponentInfo? info = pixelTypeInfo.ComponentInfo; + PixelColorType colorType = pixelTypeInfo.ColorType; + + int bitsPerComponent = info?.GetMaximumComponentPrecision() + ?? (pixelTypeInfo.BitsPerPixel <= 16 ? 16 : 32); + + int componentCount = info?.ComponentCount ?? 0; + ExrImageDataType imageDataType = colorType switch + { + PixelColorType.Luminance => ExrImageDataType.Gray, + PixelColorType.RGB or PixelColorType.BGR => ExrImageDataType.Rgb, + PixelColorType.RGB | PixelColorType.Alpha + or PixelColorType.BGR | PixelColorType.Alpha + or PixelColorType.Luminance | PixelColorType.Alpha => ExrImageDataType.Rgba, + _ => componentCount switch + { + >= 4 => ExrImageDataType.Rgba, + >= 3 => ExrImageDataType.Rgb, + 1 => ExrImageDataType.Gray, + _ => ExrImageDataType.Unknown, + } + }; + + return new() + { + PixelType = bitsPerComponent <= 16 ? ExrPixelType.Half : ExrPixelType.Float, + ImageDataType = imageDataType, + }; + } /// ExrMetadata IDeepCloneable.DeepClone() => new(this); diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs index 2cbbf0ec3..7b7917dbc 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Exr; @@ -85,4 +87,32 @@ public class ExrMetadataTests Assert.NotNull(metadata); Assert.Equal(expectedCompression, metadata.Compression); } + + [Theory] + [InlineData(PixelColorType.Binary, 1, ExrImageDataType.Unknown, ExrPixelType.Half)] + [InlineData(PixelColorType.Indexed, 8, ExrImageDataType.Unknown, ExrPixelType.Half)] + [InlineData(PixelColorType.Luminance, 16, ExrImageDataType.Gray, ExrPixelType.Half)] + [InlineData(PixelColorType.RGB, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] + [InlineData(PixelColorType.BGR, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] + [InlineData(PixelColorType.RGB | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.BGR | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.Luminance | PixelColorType.Alpha, 32, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.YCbCr, 48, ExrImageDataType.Unknown, ExrPixelType.Float)] + [InlineData(PixelColorType.CMYK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] + [InlineData(PixelColorType.YCCK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] + public void FromFormatConnectingMetadata_ConvertColorTypeAsExpected(PixelColorType pixelColorType, int bitsPerPixel, ExrImageDataType expectedImageDataType, ExrPixelType expectedPixelType) + { + FormatConnectingMetadata formatConnectingMetadata = new() + { + PixelTypeInfo = new PixelTypeInfo(bitsPerPixel) + { + ColorType = pixelColorType, + }, + }; + + ExrMetadata actual = ExrMetadata.FromFormatConnectingMetadata(formatConnectingMetadata); + + Assert.Equal(expectedImageDataType, actual.ImageDataType); + Assert.Equal(expectedPixelType, actual.PixelType); + } } From 6998d43c617d1d97aa48bef3d4ca0c39017962c8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Apr 2026 23:54:55 +1000 Subject: [PATCH 153/240] Update Convolution2PassProcessor{TPixel}.cs --- .../Convolution/Convolution2PassProcessor{TPixel}.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 15ea98936..1bbbdb350 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -107,8 +106,6 @@ internal class Convolution2PassProcessor : ImageProcessor mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - // Horizontal convolution HorizontalConvolutionRowOperation horizontalOperation = new( interest, From 420ae7444c1beebd5233466ce9b3b84fcca951cf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Apr 2026 16:24:26 +1000 Subject: [PATCH 154/240] Optimize ScaledCopyTo for common scale factors --- .../DownScalingComponentProcessor2.cs | 339 +++++++++++++++++- .../DownScalingComponentProcessor4.cs | 281 ++++++++++++++- .../DownScalingComponentProcessor8.cs | 154 +++++++- 3 files changed, 733 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs index 300a77331..23b81d2ba 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs @@ -66,32 +66,335 @@ internal sealed class DownScalingComponentProcessor2 : ComponentProcessor [MethodImpl(InliningOptions.ShortMethod)] public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) { - // TODO: Optimize: implement all cases with scale-specific, loopless code! + if (horizontalScale == 1 && verticalScale == 1) + { + CopyTo1x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + CopyTo2x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 1) + { + CopyTo2x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 1 && verticalScale == 2) + { + CopyTo1x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 1) + { + CopyTo4x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 2) + { + CopyTo4x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 1 && verticalScale == 4) + { + CopyTo1x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 4) + { + CopyTo2x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 4) + { + CopyTo4x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + // The common 1x, 2x, and 4x integral scales are specialized above. + // Uncommon legal factor-3 scales use the generic fallback. CopyArbitraryScale(ref block, ref destRef, (uint)destStrideWidth, (uint)horizontalScale, (uint)verticalScale); + } + + /// + /// Copies a 4x4 reduced block directly into the destination buffer when no chroma expansion is needed. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 2u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 3u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while doubling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 2u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 3u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while doubling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 4u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 5u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 6u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 7u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while doubling both axes. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 4u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 5u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 6u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 7u, areaStride); + } - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) + /// + /// Copies a 4x4 reduced block into the destination buffer while quadrupling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 2u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 3u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while quadrupling horizontally and doubling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 4u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 5u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 6u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 7u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while quadrupling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + CopyRow4(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while doubling horizontally and quadrupling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + WidenRow4(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + } + + /// + /// Copies a 4x4 reduced block into the destination buffer while quadrupling both axes. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + ExpandRow4(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + } + + /// + /// Copies one four-sample row from the reduced block to the destination row. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyRow4(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + Unsafe.CopyBlock( + ref Unsafe.As(ref dest), + ref Unsafe.As(ref source), + 4u * sizeof(float)); + } + + /// + /// Expands one four-sample row to eight samples by duplicating each source value horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void WidenRow4(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + float value0 = source; + float value1 = Unsafe.Add(ref source, 1u); + float value2 = Unsafe.Add(ref source, 2u); + float value3 = Unsafe.Add(ref source, 3u); + + dest = value0; + Unsafe.Add(ref dest, 1u) = value0; + Unsafe.Add(ref dest, 2u) = value1; + Unsafe.Add(ref dest, 3u) = value1; + Unsafe.Add(ref dest, 4u) = value2; + Unsafe.Add(ref dest, 5u) = value2; + Unsafe.Add(ref dest, 6u) = value3; + Unsafe.Add(ref dest, 7u) = value3; + } + + /// + /// Expands one four-sample row to sixteen samples by duplicating each source value four times horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExpandRow4(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + float value0 = source; + float value1 = Unsafe.Add(ref source, 1u); + float value2 = Unsafe.Add(ref source, 2u); + float value3 = Unsafe.Add(ref source, 3u); + + dest = value0; + Unsafe.Add(ref dest, 1u) = value0; + Unsafe.Add(ref dest, 2u) = value0; + Unsafe.Add(ref dest, 3u) = value0; + Unsafe.Add(ref dest, 4u) = value1; + Unsafe.Add(ref dest, 5u) = value1; + Unsafe.Add(ref dest, 6u) = value1; + Unsafe.Add(ref dest, 7u) = value1; + Unsafe.Add(ref dest, 8u) = value2; + Unsafe.Add(ref dest, 9u) = value2; + Unsafe.Add(ref dest, 10u) = value2; + Unsafe.Add(ref dest, 11u) = value2; + Unsafe.Add(ref dest, 12u) = value3; + Unsafe.Add(ref dest, 13u) = value3; + Unsafe.Add(ref dest, 14u) = value3; + Unsafe.Add(ref dest, 15u) = value3; + } + + /// + /// Replicates each reduced sample into an arbitrary integral expansion rectangle for uncommon subsampling ratios. + /// + [MethodImpl(InliningOptions.ColdPath)] + private static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) + { + for (nuint y = 0u; y < 4u; y++) { - for (nuint y = 0; y < 4; y++) + nuint yy = y * verticalScale; + nuint y8 = y * 8u; + + for (nuint x = 0u; x < 4u; x++) { - nuint yy = y * verticalScale; - nuint y8 = y * 8; + nuint xx = x * horizontalScale; - for (nuint x = 0; x < 4; x++) - { - nuint xx = x * horizontalScale; + float value = block[y8 + x]; - float value = block[y8 + x]; + for (nuint i = 0u; i < verticalScale; i++) + { + nuint baseIdx = ((yy + i) * areaStride) + xx; - for (nuint i = 0; i < verticalScale; i++) + for (nuint j = 0u; j < horizontalScale; j++) { - nuint baseIdx = ((yy + i) * areaStride) + xx; - - for (nuint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs index 798416990..a645a88c9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs @@ -66,32 +66,277 @@ internal sealed class DownScalingComponentProcessor4 : ComponentProcessor [MethodImpl(InliningOptions.ShortMethod)] public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) { - // TODO: Optimize: implement all cases with scale-specific, loopless code! + if (horizontalScale == 1 && verticalScale == 1) + { + CopyTo1x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + CopyTo2x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 1) + { + CopyTo2x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 1 && verticalScale == 2) + { + CopyTo1x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 1) + { + CopyTo4x1Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 2) + { + CopyTo4x2Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 1 && verticalScale == 4) + { + CopyTo1x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 4) + { + CopyTo2x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 4) + { + CopyTo4x4Scale(ref block, ref destRef, (uint)destStrideWidth); + return; + } + + // The common 1x, 2x, and 4x integral scales are specialized above. + // Uncommon legal factor-3 scales use the generic fallback. CopyArbitraryScale(ref block, ref destRef, (uint)destStrideWidth, (uint)horizontalScale, (uint)verticalScale); + } + + /// + /// Copies a 2x2 reduced block directly into the destination buffer when no chroma expansion is needed. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while doubling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while doubling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while doubling both axes. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + } - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) + /// + /// Copies a 2x2 reduced block into the destination buffer while quadrupling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x1Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while quadrupling horizontally and doubling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x2Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while quadrupling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + CopyRow2(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while doubling horizontally and quadrupling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + WidenRow2(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + } + + /// + /// Copies a 2x2 reduced block into the destination buffer while quadrupling both axes. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x4Scale(ref Block8x8F block, ref float areaOrigin, uint areaStride) + { + ref float sourceBase = ref Unsafe.As(ref block); + + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + ExpandRow2(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + } + + /// + /// Copies one two-sample row from the reduced block to the destination row. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyRow2(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + Unsafe.CopyBlock( + ref Unsafe.As(ref dest), + ref Unsafe.As(ref source), + 2u * sizeof(float)); + } + + /// + /// Expands one two-sample row to four samples by duplicating each source value horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void WidenRow2(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + float value0 = source; + float value1 = Unsafe.Add(ref source, 1u); + + dest = value0; + Unsafe.Add(ref dest, 1u) = value0; + Unsafe.Add(ref dest, 2u) = value1; + Unsafe.Add(ref dest, 3u) = value1; + } + + /// + /// Expands one two-sample row to eight samples by duplicating each source value four times horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExpandRow2(ref float sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref float source = ref Unsafe.Add(ref sourceBase, sourceRow * 8u); + ref float dest = ref Unsafe.Add(ref areaOrigin, destRow * areaStride); + + float value0 = source; + float value1 = Unsafe.Add(ref source, 1u); + + dest = value0; + Unsafe.Add(ref dest, 1u) = value0; + Unsafe.Add(ref dest, 2u) = value0; + Unsafe.Add(ref dest, 3u) = value0; + Unsafe.Add(ref dest, 4u) = value1; + Unsafe.Add(ref dest, 5u) = value1; + Unsafe.Add(ref dest, 6u) = value1; + Unsafe.Add(ref dest, 7u) = value1; + } + + /// + /// Replicates each reduced sample into an arbitrary integral expansion rectangle for uncommon subsampling ratios. + /// + [MethodImpl(InliningOptions.ColdPath)] + private static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) + { + for (nuint y = 0u; y < 2u; y++) { - for (nuint y = 0; y < 2; y++) + nuint yy = y * verticalScale; + nuint y8 = y * 8u; + + for (nuint x = 0u; x < 2u; x++) { - nuint yy = y * verticalScale; - nuint y8 = y * 8; + nuint xx = x * horizontalScale; - for (nuint x = 0; x < 2; x++) - { - nuint xx = x * horizontalScale; + float value = block[y8 + x]; - float value = block[y8 + x]; + for (nuint i = 0u; i < verticalScale; i++) + { + nuint baseIdx = ((yy + i) * areaStride) + xx; - for (nuint i = 0; i < verticalScale; i++) + for (nuint j = 0u; j < horizontalScale; j++) { - nuint baseIdx = ((yy + i) * areaStride) + xx; - - for (nuint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs index f3b09e6b4..ef17bf002 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs @@ -63,16 +63,56 @@ internal sealed class DownScalingComponentProcessor8 : ComponentProcessor return; } + if (horizontalScale == 2 && verticalScale == 1) + { + CopyTo2x1Scale(value, ref destRef); + return; + } + + if (horizontalScale == 1 && verticalScale == 2) + { + CopyTo1x2Scale(value, ref destRef, (uint)destStrideWidth); + return; + } + if (horizontalScale == 2 && verticalScale == 2) { - destRef = value; - Unsafe.Add(ref destRef, 1) = value; - Unsafe.Add(ref destRef, 0 + (uint)destStrideWidth) = value; - Unsafe.Add(ref destRef, 1 + (uint)destStrideWidth) = value; + CopyTo2x2Scale(value, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 1) + { + CopyTo4x1Scale(value, ref destRef); + return; + } + + if (horizontalScale == 4 && verticalScale == 2) + { + CopyTo4x2Scale(value, ref destRef, (uint)destStrideWidth); return; } - // TODO: Optimize: implement all cases with scale-specific, loopless code! + if (horizontalScale == 1 && verticalScale == 4) + { + CopyTo1x4Scale(value, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 2 && verticalScale == 4) + { + CopyTo2x4Scale(value, ref destRef, (uint)destStrideWidth); + return; + } + + if (horizontalScale == 4 && verticalScale == 4) + { + CopyTo4x4Scale(value, ref destRef, (uint)destStrideWidth); + return; + } + + // The common 1x, 2x, and 4x integral scales are specialized above. + // Uncommon legal factor-3 scales use the generic fallback. for (nuint y = 0; y < (uint)verticalScale; y++) { for (nuint x = 0; x < (uint)horizontalScale; x++) @@ -83,4 +123,108 @@ internal sealed class DownScalingComponentProcessor8 : ComponentProcessor destRef = ref Unsafe.Add(ref destRef, (uint)destStrideWidth); } } + + /// + /// Writes a single source value to two horizontally adjacent samples. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x1Scale(float value, ref float areaOrigin) + { + areaOrigin = value; + Unsafe.Add(ref areaOrigin, 1u) = value; + } + + /// + /// Writes a single source value to two vertically adjacent samples. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x2Scale(float value, ref float areaOrigin, uint areaStride) + { + areaOrigin = value; + Unsafe.Add(ref areaOrigin, areaStride) = value; + } + + /// + /// Writes a single source value to a 2x2 rectangle. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x2Scale(float value, ref float areaOrigin, uint areaStride) + { + areaOrigin = value; + Unsafe.Add(ref areaOrigin, 1u) = value; + Unsafe.Add(ref areaOrigin, areaStride) = value; + Unsafe.Add(ref areaOrigin, areaStride + 1u) = value; + } + + /// + /// Writes a single source value to four horizontally adjacent samples. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x1Scale(float value, ref float areaOrigin) + { + areaOrigin = value; + Unsafe.Add(ref areaOrigin, 1u) = value; + Unsafe.Add(ref areaOrigin, 2u) = value; + Unsafe.Add(ref areaOrigin, 3u) = value; + } + + /// + /// Writes a single source value to a 4x2 rectangle. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x2Scale(float value, ref float areaOrigin, uint areaStride) + { + CopyTo4x1Scale(value, ref areaOrigin); + + ref float nextRow = ref Unsafe.Add(ref areaOrigin, areaStride); + CopyTo4x1Scale(value, ref nextRow); + } + + /// + /// Writes a single source value to four vertically adjacent samples. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo1x4Scale(float value, ref float areaOrigin, uint areaStride) + { + areaOrigin = value; + Unsafe.Add(ref areaOrigin, areaStride) = value; + Unsafe.Add(ref areaOrigin, areaStride * 2u) = value; + Unsafe.Add(ref areaOrigin, areaStride * 3u) = value; + } + + /// + /// Writes a single source value to a 2x4 rectangle. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo2x4Scale(float value, ref float areaOrigin, uint areaStride) + { + CopyTo2x1Scale(value, ref areaOrigin); + + ref float row1 = ref Unsafe.Add(ref areaOrigin, areaStride); + CopyTo2x1Scale(value, ref row1); + + ref float row2 = ref Unsafe.Add(ref areaOrigin, areaStride * 2u); + CopyTo2x1Scale(value, ref row2); + + ref float row3 = ref Unsafe.Add(ref areaOrigin, areaStride * 3u); + CopyTo2x1Scale(value, ref row3); + } + + /// + /// Writes a single source value to a 4x4 rectangle. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyTo4x4Scale(float value, ref float areaOrigin, uint areaStride) + { + CopyTo4x1Scale(value, ref areaOrigin); + + ref float row1 = ref Unsafe.Add(ref areaOrigin, areaStride); + CopyTo4x1Scale(value, ref row1); + + ref float row2 = ref Unsafe.Add(ref areaOrigin, areaStride * 2u); + CopyTo4x1Scale(value, ref row2); + + ref float row3 = ref Unsafe.Add(ref areaOrigin, areaStride * 3u); + CopyTo4x1Scale(value, ref row3); + } } From cde366b5cd9bf5d4d202ac303d71e6f7590a7bb6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Apr 2026 21:55:14 +1000 Subject: [PATCH 155/240] Remove premature rounding during normalization --- .../Jpeg/Components/Block8x8F.Vector128.cs | 40 +++++----- .../Jpeg/Components/Block8x8F.Vector256.cs | 26 +++--- .../Formats/Jpeg/Components/Block8x8F.cs | 79 +++++++------------ .../DirectComponentProcessor.cs | 8 +- .../Jpeg/Components/ScaledFloatingPointDCT.cs | 14 ++-- .../Formats/Jpg/Block8x8FTests.cs | 71 ----------------- .../ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 10 +-- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 2 +- .../Jpg/JpegDecoderTests.Progressive.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.cs | 2 +- .../Formats/Tiff/TiffDecoderTests.cs | 14 ++-- ...ToReferenceOutput_Rgba32_forest_bridge.png | 4 +- ...ToReferenceOutput_Rgba32_forest_bridge.png | 4 +- .../DecodeBaselineJpeg_Calliphora.png | 4 +- ...Issue394-MultiHuffmanBaseline-Speakers.png | 4 +- ...codeBaselineJpeg_MultiScanBaselineCMYK.png | 4 +- .../DecodeBaselineJpeg_badeof.png | 4 +- .../DecodeBaselineJpeg_badrst.png | 4 +- .../DecodeBaselineJpeg_cmyk.png | 4 +- .../DecodeBaselineJpeg_jpeg420small.png | 4 +- .../DecodeBaselineJpeg_jpeg422.png | 4 +- .../DecodeBaselineJpeg_jpeg444.png | 4 +- .../DecodeBaselineJpeg_testorig.png | 4 +- .../DecodeBaselineJpeg_testorig12.png | 4 +- ...DecodeBaselineJpeg_ycck-subsample-1222.png | 4 +- .../DecodeBaselineJpeg_ycck.png | 4 +- ...ecodeProgressiveJpeg_BadEofProgressive.png | 4 +- .../DecodeProgressiveJpeg_ExifUndefType.png | 4 +- .../DecodeProgressiveJpeg_fb.png | 4 +- .../DecodeProgressiveJpeg_progress.png | 4 +- .../Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png | 4 +- ...GB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png | 4 +- ...GB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png | 4 +- ..._ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png | 4 +- ...GB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png | 4 +- ...RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png | 4 +- ...de_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png | 4 +- ...B_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png | 4 +- .../Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png | 4 +- .../Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png | 4 +- ...Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png | 4 +- ...code_Resize_Bicubic_Calliphora_150_150.png | 4 +- ...coder_Decode_Resize_Calliphora_150_150.png | 4 +- ...zed_Combined_Resize_Calliphora_150_150.png | 4 +- ...ialized_IDCT_Resize_Calliphora_150_150.png | 4 +- ...alized_Scale_Resize_Calliphora_150_150.png | 4 +- ...ecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png | 4 +- ..._JpegCompressedWithIssue2679_Issue2679.png | 4 +- ..._CanDecode_YccK_ICC_Rgba32_Issue2454_A.png | 4 +- ..._CanDecode_YccK_ICC_Rgba32_Issue2454_B.png | 4 +- ...oder_CanDecode_YccK_Rgba32_Issue2454_A.png | 4 +- ...oder_CanDecode_YccK_Rgba32_Issue2454_B.png | 4 +- ...EntropyCrop_MultiScanBaselineCMYK_0.25.png | 4 +- ...EntropyCrop_MultiScanBaselineCMYK_0.75.png | 4 +- 54 files changed, 175 insertions(+), 265 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs index d4c0398d9..a80dcf86e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs @@ -13,31 +13,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components; internal partial struct Block8x8F { /// - /// version of and . + /// version of . /// /// The maximum value to normalize to. [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector128(float maximum) + public void NormalizeColorsInPlaceVector128(float maximum) { Vector128 max = Vector128.Create(maximum); Vector128 off = Vector128.Ceiling(max * .5F); - this.V0L = NormalizeAndRoundVector128(this.V0L.AsVector128(), off, max).AsVector4(); - this.V0R = NormalizeAndRoundVector128(this.V0R.AsVector128(), off, max).AsVector4(); - this.V1L = NormalizeAndRoundVector128(this.V1L.AsVector128(), off, max).AsVector4(); - this.V1R = NormalizeAndRoundVector128(this.V1R.AsVector128(), off, max).AsVector4(); - this.V2L = NormalizeAndRoundVector128(this.V2L.AsVector128(), off, max).AsVector4(); - this.V2R = NormalizeAndRoundVector128(this.V2R.AsVector128(), off, max).AsVector4(); - this.V3L = NormalizeAndRoundVector128(this.V3L.AsVector128(), off, max).AsVector4(); - this.V3R = NormalizeAndRoundVector128(this.V3R.AsVector128(), off, max).AsVector4(); - this.V4L = NormalizeAndRoundVector128(this.V4L.AsVector128(), off, max).AsVector4(); - this.V4R = NormalizeAndRoundVector128(this.V4R.AsVector128(), off, max).AsVector4(); - this.V5L = NormalizeAndRoundVector128(this.V5L.AsVector128(), off, max).AsVector4(); - this.V5R = NormalizeAndRoundVector128(this.V5R.AsVector128(), off, max).AsVector4(); - this.V6L = NormalizeAndRoundVector128(this.V6L.AsVector128(), off, max).AsVector4(); - this.V6R = NormalizeAndRoundVector128(this.V6R.AsVector128(), off, max).AsVector4(); - this.V7L = NormalizeAndRoundVector128(this.V7L.AsVector128(), off, max).AsVector4(); - this.V7R = NormalizeAndRoundVector128(this.V7R.AsVector128(), off, max).AsVector4(); + this.V0L = NormalizeVector128(this.V0L.AsVector128(), off, max).AsVector4(); + this.V0R = NormalizeVector128(this.V0R.AsVector128(), off, max).AsVector4(); + this.V1L = NormalizeVector128(this.V1L.AsVector128(), off, max).AsVector4(); + this.V1R = NormalizeVector128(this.V1R.AsVector128(), off, max).AsVector4(); + this.V2L = NormalizeVector128(this.V2L.AsVector128(), off, max).AsVector4(); + this.V2R = NormalizeVector128(this.V2R.AsVector128(), off, max).AsVector4(); + this.V3L = NormalizeVector128(this.V3L.AsVector128(), off, max).AsVector4(); + this.V3R = NormalizeVector128(this.V3R.AsVector128(), off, max).AsVector4(); + this.V4L = NormalizeVector128(this.V4L.AsVector128(), off, max).AsVector4(); + this.V4R = NormalizeVector128(this.V4R.AsVector128(), off, max).AsVector4(); + this.V5L = NormalizeVector128(this.V5L.AsVector128(), off, max).AsVector4(); + this.V5R = NormalizeVector128(this.V5R.AsVector128(), off, max).AsVector4(); + this.V6L = NormalizeVector128(this.V6L.AsVector128(), off, max).AsVector4(); + this.V6R = NormalizeVector128(this.V6R.AsVector128(), off, max).AsVector4(); + this.V7L = NormalizeVector128(this.V7L.AsVector128(), off, max).AsVector4(); + this.V7R = NormalizeVector128(this.V7R.AsVector128(), off, max).AsVector4(); } /// @@ -71,8 +71,8 @@ internal partial struct Block8x8F } [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 NormalizeAndRoundVector128(Vector128 value, Vector128 off, Vector128 max) - => Vector128_.RoundToNearestInteger(Vector128_.Clamp(value + off, Vector128.Zero, max)); + private static Vector128 NormalizeVector128(Vector128 value, Vector128 off, Vector128 max) + => Vector128_.Clamp(value + off, Vector128.Zero, max); private static void MultiplyIntoInt16Vector128(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs index 2aaf5c943..f16452ed5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs @@ -39,23 +39,23 @@ internal partial struct Block8x8F #pragma warning restore SA1310 // Field names should not contain underscore /// - /// version of and . + /// version of . /// /// The maximum value to normalize to. [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector256(float maximum) + public void NormalizeColorsInPlaceVector256(float maximum) { Vector256 max = Vector256.Create(maximum); Vector256 off = Vector256.Ceiling(max * .5F); - this.V256_0 = NormalizeAndRoundVector256(this.V256_0, off, max); - this.V256_1 = NormalizeAndRoundVector256(this.V256_1, off, max); - this.V256_2 = NormalizeAndRoundVector256(this.V256_2, off, max); - this.V256_3 = NormalizeAndRoundVector256(this.V256_3, off, max); - this.V256_4 = NormalizeAndRoundVector256(this.V256_4, off, max); - this.V256_5 = NormalizeAndRoundVector256(this.V256_5, off, max); - this.V256_6 = NormalizeAndRoundVector256(this.V256_6, off, max); - this.V256_7 = NormalizeAndRoundVector256(this.V256_7, off, max); + this.V256_0 = NormalizeVector256(this.V256_0, off, max); + this.V256_1 = NormalizeVector256(this.V256_1, off, max); + this.V256_2 = NormalizeVector256(this.V256_2, off, max); + this.V256_3 = NormalizeVector256(this.V256_3, off, max); + this.V256_4 = NormalizeVector256(this.V256_4, off, max); + this.V256_5 = NormalizeVector256(this.V256_5, off, max); + this.V256_6 = NormalizeVector256(this.V256_6, off, max); + this.V256_7 = NormalizeVector256(this.V256_7, off, max); } /// @@ -95,10 +95,10 @@ internal partial struct Block8x8F } [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 NormalizeAndRoundVector256(Vector256 value, Vector256 off, Vector256 max) - => Vector256_.RoundToNearestInteger(Vector256_.Clamp(value + off, Vector256.Zero, max)); + private static Vector256 NormalizeVector256(Vector256 value, Vector256 off, Vector256 max) + => Vector256_.Clamp(value + off, Vector256.Zero, max); - private static unsafe void MultiplyIntoInt16Vector256(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) + private static void MultiplyIntoInt16Vector256(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) { DebugGuard.IsTrue(Vector256.IsHardwareAccelerated, "Vector256 support is required to run this operation!"); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 49b519201..19a695d07 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -132,7 +132,7 @@ internal partial struct Block8x8F : IEquatable /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void ScaledCopyTo(float[] dest) + public void ScaledCopyTo(float[] dest) { DebugGuard.MustBeGreaterThanOrEqualTo(dest.Length, Size, "dest is too small"); @@ -193,7 +193,7 @@ internal partial struct Block8x8F : IEquatable /// /// The other block. [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void MultiplyInPlace(ref Block8x8F other) + public void MultiplyInPlace(ref Block8x8F other) { if (Vector256.IsHardwareAccelerated) { @@ -324,62 +324,43 @@ internal partial struct Block8x8F : IEquatable } /// - /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. + /// Level shift by +maximum/2, clip to [0, maximum] /// - /// The maximum value. - public void NormalizeColorsAndRoundInPlace(float maximum) + /// The maximum value to normalize to. + public void NormalizeColorsInPlace(float maximum) { if (Vector256.IsHardwareAccelerated) { - this.NormalizeColorsAndRoundInPlaceVector256(maximum); + this.NormalizeColorsInPlaceVector256(maximum); + return; } else if (Vector128.IsHardwareAccelerated) { - this.NormalizeColorsAndRoundInPlaceVector128(maximum); + this.NormalizeColorsInPlaceVector128(maximum); + return; } else { - this.NormalizeColorsInPlace(maximum); - this.RoundInPlace(); - } - } - - /// - /// Level shift by +maximum/2, clip to [0, maximum] - /// - /// The maximum value to normalize to. - public void NormalizeColorsInPlace(float maximum) - { - Vector4 min = Vector4.Zero; - Vector4 max = new(maximum); - Vector4 off = new(MathF.Ceiling(maximum * 0.5F)); - - this.V0L = Vector4.Clamp(this.V0L + off, min, max); - this.V0R = Vector4.Clamp(this.V0R + off, min, max); - this.V1L = Vector4.Clamp(this.V1L + off, min, max); - this.V1R = Vector4.Clamp(this.V1R + off, min, max); - this.V2L = Vector4.Clamp(this.V2L + off, min, max); - this.V2R = Vector4.Clamp(this.V2R + off, min, max); - this.V3L = Vector4.Clamp(this.V3L + off, min, max); - this.V3R = Vector4.Clamp(this.V3R + off, min, max); - this.V4L = Vector4.Clamp(this.V4L + off, min, max); - this.V4R = Vector4.Clamp(this.V4R + off, min, max); - this.V5L = Vector4.Clamp(this.V5L + off, min, max); - this.V5R = Vector4.Clamp(this.V5R + off, min, max); - this.V6L = Vector4.Clamp(this.V6L + off, min, max); - this.V6R = Vector4.Clamp(this.V6R + off, min, max); - this.V7L = Vector4.Clamp(this.V7L + off, min, max); - this.V7R = Vector4.Clamp(this.V7R + off, min, max); - } - - /// - /// Rounds all values in the block. - /// - public void RoundInPlace() - { - for (int i = 0; i < Size; i++) - { - this[i] = MathF.Round(this[i]); + Vector4 min = Vector4.Zero; + Vector4 max = new(maximum); + Vector4 off = new(MathF.Ceiling(maximum * 0.5F)); + + this.V0L = Vector4.Clamp(this.V0L + off, min, max); + this.V0R = Vector4.Clamp(this.V0R + off, min, max); + this.V1L = Vector4.Clamp(this.V1L + off, min, max); + this.V1R = Vector4.Clamp(this.V1R + off, min, max); + this.V2L = Vector4.Clamp(this.V2L + off, min, max); + this.V2R = Vector4.Clamp(this.V2R + off, min, max); + this.V3L = Vector4.Clamp(this.V3L + off, min, max); + this.V3R = Vector4.Clamp(this.V3R + off, min, max); + this.V4L = Vector4.Clamp(this.V4L + off, min, max); + this.V4R = Vector4.Clamp(this.V4R + off, min, max); + this.V5L = Vector4.Clamp(this.V5L + off, min, max); + this.V5R = Vector4.Clamp(this.V5R + off, min, max); + this.V6L = Vector4.Clamp(this.V6L + off, min, max); + this.V6R = Vector4.Clamp(this.V6R + off, min, max); + this.V7L = Vector4.Clamp(this.V7L + off, min, max); + this.V7R = Vector4.Clamp(this.V7R + off, min, max); } } @@ -533,7 +514,7 @@ internal partial struct Block8x8F : IEquatable } /// - public bool Equals(Block8x8F other) + public readonly bool Equals(Block8x8F other) => this.V0L == other.V0L && this.V0R == other.V0R && this.V1L == other.V1L diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs index 79e25a67a..b4399a69c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs @@ -53,10 +53,10 @@ internal sealed class DirectComponentProcessor : ComponentProcessor // Convert from spectral to color FloatingPointDCT.TransformIDCT(ref workspaceBlock); - // To conform better to libjpeg we actually NEED TO loose precision here. - // This is because they store blocks as Int16 between all the operations. - // To be "more accurate", we need to emulate this by rounding! - workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); + // Normalize into the component sample range without quantizing away + // fractional precision. The later color conversion / final pack stage + // performs the only rounding we actually need for output samples. + workspaceBlock.NormalizeColorsInPlace(maximumValue); // Write to color buffer acording to sampling factors int xColorBufferStart = xBlock * this.BlockAreaSize.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs index b8234ff3e..b4d32f709 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs @@ -136,10 +136,10 @@ internal static class ScaledFloatingPointDCT (z4 * FP32_2_562915447); // Save results to the top left 4x4 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp2) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 3] = MathF.Round(Numerics.Clamp(((tmp10 - tmp2) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp12 + tmp0) * 0.5F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 2] = MathF.Round(Numerics.Clamp(((tmp12 - tmp0) * 0.5F) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 0] = Numerics.Clamp(((tmp10 + tmp2) * 0.5F) + normalizationValue, 0, maxValue); + block[(ctr * 8) + 3] = Numerics.Clamp(((tmp10 - tmp2) * 0.5F) + normalizationValue, 0, maxValue); + block[(ctr * 8) + 1] = Numerics.Clamp(((tmp12 + tmp0) * 0.5F) + normalizationValue, 0, maxValue); + block[(ctr * 8) + 2] = Numerics.Clamp(((tmp12 - tmp0) * 0.5F) + normalizationValue, 0, maxValue); } } @@ -199,8 +199,8 @@ internal static class ScaledFloatingPointDCT (block[ctr + (8 * 1) + 2] * FP32_3_624509785); // Save results to the top left 2x2 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp0) * 0.25F) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp10 - tmp0) * 0.25F) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 0] = Numerics.Clamp(((tmp10 + tmp0) * 0.25F) + normalizationValue, 0, maxValue); + block[(ctr * 8) + 1] = Numerics.Clamp(((tmp10 - tmp0) * 0.25F) + normalizationValue, 0, maxValue); } } @@ -213,6 +213,6 @@ internal static class ScaledFloatingPointDCT /// Output range normalization value, 1/2 of the . /// Maximum value of the output range. public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) - => MathF.Round(Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue)); + => Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue); } #pragma warning restore IDE0078 diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 368a7b369..544ac85a5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -181,54 +181,6 @@ public partial class Block8x8FTests : JpegFixture } } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector256(int seed) - { - if (this.SkipOnNonVector256Runner()) - { - return; - } - - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); - - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector256(255); - - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector128(int seed) - { - if (this.SkipOnNonVector128Runner()) - { - return; - } - - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); - - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector128(255); - - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } - [Theory] [InlineData(1, 2)] [InlineData(2, 1)] @@ -290,29 +242,6 @@ public partial class Block8x8FTests : JpegFixture } } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void RoundInPlaceSlow(int seed) - { - Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); - - Block8x8F d = s; - d.RoundInPlace(); - - this.Output.WriteLine(s.ToString()); - this.Output.WriteLine(d.ToString()); - - for (int i = 0; i < 64; i++) - { - float expected = (float)Math.Round(s[i]); - float actual = d[i]; - - Assert.Equal(expected, actual); - } - } - [Fact] public void MultiplyInPlace_ByOtherBlock() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 50eada4c7..83c3a344d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -193,7 +193,7 @@ public static class DCTTests { for (int x = 0; x < 4; x++) { - AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x)); + AssertScaledElementEquality(expectedSpan[((y * 16) + (x * 2))..], actualSpan[((y * 8) + x)..]); } } @@ -210,7 +210,7 @@ public static class DCTTests } } - average2x2 = MathF.Round(average2x2 / 4f); + average2x2 /= 4f; Assert.Equal((int)average2x2, (int)actual[0]); } @@ -254,7 +254,7 @@ public static class DCTTests { for (int x = 0; x < 2; x++) { - AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x)); + AssertScaledElementEquality(expectedSpan[((y * 32) + (x * 4))..], actualSpan[((y * 8) + x)..]); } } @@ -271,7 +271,7 @@ public static class DCTTests } } - average4x4 = MathF.Round(average4x4 / 16f); + average4x4 /= 16f; Assert.Equal((int)average4x4, (int)actual[0]); } @@ -311,7 +311,7 @@ public static class DCTTests NormalizationValue, MaxOutputValue); - float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + float expected = Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue); Assert.Equal((int)actual, (int)expected); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index f147e4132..e7e6d2033 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -30,7 +30,7 @@ public partial class JpegDecoderTests } using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr, appendPixelTypeToFileName: false); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index a5472e1ae..fff1e085b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -21,7 +21,7 @@ public partial class JpegDecoderTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); + image.DebugSave(provider, appendPixelTypeToFileName: false); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ede147dbe..5fae85cc8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -49,7 +49,7 @@ public partial class JpegEncoderTests public static readonly TheoryData CmykEncodingSetups = new() { - { JpegColorType.Cmyk, 100, 0.0159f / 100 }, + { JpegColorType.Cmyk, 100, 0.0164f / 100 }, { JpegColorType.Cmyk, 80, 0.3922f / 100 }, { JpegColorType.Cmyk, 40, 0.6488f / 100 }, }; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index e432a7251..6123d46da 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -707,17 +707,17 @@ public class TiffDecoderTests : TiffDecoderBaseTester [WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] - [WithFile(Issues2123, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); + [WithFile(Issues2123, PixelTypes.Rgba32, 0.110f)] + public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider, float tolerance = 0.001f) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: tolerance); [Theory] [WithFile(RgbOldJpegCompressed, PixelTypes.Rgba32)] - [WithFile(RgbOldJpegCompressed2, PixelTypes.Rgba32)] + [WithFile(RgbOldJpegCompressed2, PixelTypes.Rgba32, 0.1003f)] [WithFile(RgbOldJpegCompressed3, PixelTypes.Rgba32)] [WithFile(RgbOldJpegCompressedGray, PixelTypes.Rgba32)] [WithFile(YCbCrOldJpegCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_OldJpegCompressed(TestImageProvider provider) + public void TiffDecoder_CanDecode_OldJpegCompressed(TestImageProvider provider, float tolerance = 0.001f) where TPixel : unmanaged, IPixel { DecoderOptions decoderOptions = new() @@ -728,7 +728,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.DebugSave(provider); image.CompareToOriginal( provider, - ImageComparer.Tolerant(0.001f), + ImageComparer.Tolerant(tolerance), ReferenceDecoder, decoderOptions); } @@ -784,7 +784,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester // The image is handcrafted to simulate issue 2679. ImageMagick will throw an expection here and wont decode, // so we compare to rererence output instead. - image.DebugSave(provider); + image.DebugSave(provider, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( ImageComparer.Exact, provider, diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png index de79ec729..84c7e64ee 100644 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aada4a2ccf45de24f2a591a18d9bc0260ceb3829e104fee6982061013ed87282 -size 14107709 +oid sha256:4a4d2a3a1f320d3fcb121bd8edf716354b7e8ceb251a9eaa2087f443a3adbbc1 +size 10896218 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png index ff5b35a5f..679615d17 100644 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dca9b5b890d3a79b0002b7093d254d484ada4207e5010d1f0c6248d4dd6e22db -size 13909894 +oid sha256:0c3f30908a803ac5956dbd5959b225bceab1fc673a1b89d55d1096ba024c7bcc +size 12519351 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png index 07c29c097..95ea3d472 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:362eddc5e06d672b4654bfe7a1ded995934a1c59719a3f909773b2e61931ffac -size 1332495 +oid sha256:9a6f484f06c0b466d16591db78fec3cd2c3f6ebc7578896075cd4af95d8ceaab +size 1411818 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png index 1f38ec542..828880162 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7a1e89adcd70f792d678786a177bac927a15d6065001cd76aab0bf3cc1b7b4c -size 1034853 +oid sha256:88271ffea79e42914b8c37828da65afb691c7319b8e28fdd35942ecba4087baa +size 1060013 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png index 349ff6485..e72b1a041 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9dbb00a4be909d4b7fd605f83f786ff85545da54272a256c100afd80d687e8f -size 93253 +oid sha256:3b160cc44490303c23c0abaa0dda827d68e11bfa67127e19da974073085abbe4 +size 96042 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png index 830a95bd7..ccdb19e4d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 +oid sha256:7ce1a5d71cefd6527daecb8c1d9089f63189b833f1186971ddb89c6257d076ef +size 46745 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png index 0512251ab..4f2aae2ed 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0501e53f97ddb4252e15073dc75f4129428befe5594a07297c21ae9f89f075b2 -size 316278 +oid sha256:522b0a2f9a97689217af461ab2a63a41553c0ae6ca28cde046b01019c87e2393 +size 348228 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png index 27c22ecc6..e24cc5969 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2253c83151238056caef2f3bca8800108a84360b2ff21979e4ff37fdddd2232 -size 419852 +oid sha256:5e81ac851f979c017082180a13ee4ddfe26c4151bfc082f7915252e57da44ec7 +size 416967 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png index 4032a32af..ae30676b3 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b5e1d91fb6dc1ddb696fbee63331ba9c6ef3548b619c005887e60c5b01f4981 -size 27303 +oid sha256:79291e1eddee66548156fc40bb5c8b1209c7b0048fb56f2bb7890c7d89ce655e +size 27264 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png index 018ecda7a..8ddb74880 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:733cc46271c4402974db2536a55e6ecae3110856df73031ca48dad03745d852d -size 35375 +oid sha256:5196d5a20f639d833cf1ad979a290c144c97b41897fa5f0fa7454f5f4e8d3c11 +size 28697 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png index e5ce7eb3d..acca4b44e 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6543f546fac9d05ebdac7a534b0cc422f31bfd81067212a19cb3a52ad24560a8 -size 3978 +oid sha256:9894983085f3d3d45b8c4ea9acc680932f236a4602230046386130aa2af916f1 +size 4936 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png index 830a95bd7..ccdb19e4d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 +oid sha256:7ce1a5d71cefd6527daecb8c1d9089f63189b833f1186971ddb89c6257d076ef +size 46745 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png index 23f1941dc..dc41461e0 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad57cf87eade9ee6663f575b358eafaa869a16b3b02d0fb00ca8c683422b85a0 -size 47601 +oid sha256:26ef4244aa761c7b6c51534e414f79438753b9dbd1fbf45d2f1e2fd2dd4ec4c1 +size 46636 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png index f35c40ed8..66337b5c9 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a17478333c6ef0943aeb96faba1c0ea560995d0612564fe033b72b2949f4869f -size 66748 +oid sha256:91a522b32485af3be304c2d98a60b9906112637a4627d69a072976474cef7485 +size 66932 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png index 5f41fb523..76183356b 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63f7105e9e2d0794b3f7225af2187a5717c12b806b68b33b98e08e0a0b1ffa79 -size 71051 +oid sha256:f374012655af194ebece2e9329da416be830d1cadd24a2bd584480d45751ea78 +size 58272 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png index 2386ae49a..ecf22f93c 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e5d84fa88ac8b552c21c042f155801924240d764da365440d28eb8133950925 -size 568177 +oid sha256:36b557cd6d55389b8f02b811ecd2b14f1f1c440c7e8dd5a5a3c55cea57547203 +size 564682 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png index 3de8b1557..c144554ca 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb636a9f304cdef713b823adc7424108b4d44fada294c48a366278c9b9e7b4b5 -size 20163 +oid sha256:ff9554b1f421ca04e0580fbb505895f002b80298feb128a15cdc045a1625a490 +size 20111 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png index 07a7c3573..30bfb538f 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2935d3dc66eec334eec65c1ed9fbb04046404a9c28bed413ac635878e63ea6cf -size 114688 +oid sha256:c2eb2bd4db9b67466d96fe154cd771d6780a24a798a0c051f307425f1b0e3d15 +size 115708 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png index 8241c36ac..e9a2a0bd8 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b48730a7a149e13dd87a24e62c0704895b08a5ed1f8ea48a6a6a7248450750d -size 301416 +oid sha256:c5c5b5711041f22e8ee1afb344137c00ee80ba51e777689513e8dfeea855befa +size 297941 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png index 77a9d0d9c..4e554a34d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:215cba73dfb0e19f75f6dc0c3fefca474bd65f57684a207a11d896e1637bb643 -size 1240827 +oid sha256:9750b54f5220020e0c3f5ce22e4f108cb70290750c613c29beeb49473d5c80a6 +size 1349665 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png index 0963e90b7..37e1e37cd 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c942b534baa51b8e46e88bd38d1ced319bccf1b55a5711ae5761697b7437fe4e -size 458321 +oid sha256:dcc32a36dcdfe3b9cfae17a81d3eb180248db4507ae93fdd4bb92e1e5e29950d +size 466215 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png index 1e5eaca4d..dbfa40378 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:163711226bdfa2f102b314435baea9f69ad1be1b11ef5ad8348358cd09a029ae -size 482963 +oid sha256:ae9a32eba21fa0bd2039803b5af8ffdba0409c104746455d5028741f214eb1a1 +size 479922 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png index 06bee00d7..5427b5c95 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7981f40bab5bffff3e7c9ea1676d224c173fabbaa6e7a920d7a9dabd58655f -size 464917 +oid sha256:e2bf66bb6d7eb3ff402b8564fb51bc951792ad47dc17fc9c7c886744638a59bb +size 469096 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png index 3ae12c657..787044068 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11b02b982c024e295915e88f56a428ad73068217a7ae625f705127ab8c35a4bf -size 477061 +oid sha256:e9c015e599b1710fd50c4ad61104fb03f27f211c13686cb162baba492ec4a935 +size 482675 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png index 0a2eb91cc..2ee122b8d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a49967c20cccf824df4de3f105f5ddb44d7a602c072ce22caa38939f21f62505 -size 469827 +oid sha256:e25ba3afb3346633db52e91062c4266474bc9d9c0b3fcf686f1040b3a4d70d0d +size 476050 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png index fa554484b..a1e876d2b 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b33fc8fd03142aaaf8aabc39e084acd9e82e9222292e281953d92d65edcc1a7 -size 436111 +oid sha256:b80e88a7fdb27491687202c247eafbeb9d1c024affd5dc4633bcf15602fed990 +size 464921 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png index a0b73d299..8a27087f5 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe06798b92c9b476c167407e752b4379d50f1b1ad6329eceb368c8c36097b401 -size 95103 +oid sha256:dbe96e449dfeadc71bbfb0b48d2f56e6357d5b64a225153eed60263ea3356e71 +size 119987 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png index 99ae53f93..586bc9647 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21f8d54d4b789b783f3020402d4c1b91bb541de6565e2960976b569f60694631 -size 99385 +oid sha256:6e9e4da17b7270565fd592f4ff710ff317e1054794d966d33f11748defc7c35b +size 124944 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png index 759b26a60..5164defc1 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18ad361f79b4ab26d452d5cc7ada4c121dfbf45d20da7c23a58f71a9497d17a2 -size 5341 +oid sha256:0eaf217570e283b6fb9a7d52ba5087d760d9a98827e507cbae299915871e1c31 +size 2595 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png index bf9556683..a73e5f31e 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cef85199e8560d6669766c094d078831024e44ac7fc537f8696f802c8e06138b -size 420141 +oid sha256:de73fa7a2cdd79763e40c51381f9a0cf5c7d43db2dcd7a86dad0a5b68377665f +size 421249 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png index e982d9034..16df99e04 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bb6ed717a2af582d60ccd6c1c9c1ac92df0f8662755530b7e9063724835b23b -size 27709 +oid sha256:9be82bf220095b5650c7cd0a0eba9ee2b0fbe39881ff2bbde465ca9d2f94490f +size 27803 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png index 65aac22e9..98aac2c55 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 +oid sha256:b7e4b589fdbad2688895317fc760a834624f76cc9a027a65ce3cd6b35563ec85 +size 28514 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png index 65aac22e9..98aac2c55 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 +oid sha256:b7e4b589fdbad2688895317fc760a834624f76cc9a027a65ce3cd6b35563ec85 +size 28514 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png index abe31b2be..665967330 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc67170d70378ad8b8c0e1c1695b5c268341f0d26a6c788d1a8dffa8c90482a0 -size 102165 +oid sha256:d28335f6ad2421b6bc7e420c2e61ead73cd64f63ba7e1c9bd98ac1c140188ddb +size 104381 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png index 87087adc5..9c01562f1 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae7f6ebfd9f2ddd85611827fda13eaf316d36d5187900458568f80b929effb9b -size 28291 +oid sha256:d0db0474f28d6302aae207b09f63e2de9e9ce6a9777a95dbac69ccb957d08daa +size 28381 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png index 06d60e030..6f35ecb55 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f68db78d765a7f36570cd7b57a1f06cfca24c3b4916d0692a4aa051209ec327 -size 616 +oid sha256:b1ff0ce13bf0521b8a6f0d77806473c837c45428391786f751186a53fd951c0c +size 394 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 6150aacb3..d2f11dc17 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:6cd36c7e07a08e22cceecd28a056c80e5553a8c092bfc091e902d13bd5c46f4d -size 120054 +oid sha256:96729898fee87fc4305913c111a5841af205564d032b916aeb04425a20c6b22c +size 46540 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png index 97118c15b..addaa27bf 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4f77673028643af0ac02a8f6a1e2db14052177e3401c369391a8ff7e943770c -size 7679254 +oid sha256:13f51f69b061303d06b73fa7e626d7252a9fd14aa84c961ea894f6e7cd7218cc +size 7603818 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png index 52accc22d..445cad99c 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e616895c21fd8b19a216e8a3ef4968bd413589b5875efdac29860f019a710527 -size 7517284 +oid sha256:b6a35853e44d06a6f74e3aaf558e6a4b9c3209c6c3fa2c8f3ee650bf049ac0d3 +size 7624359 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png index 350d1af68..e88a98fa4 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7911e059049c427229136479740fd62e2e09907549ec3e1421a6a60da6167cc -size 7840892 +oid sha256:9e91f6ae534b95a9e052158043e929e1da22e0851b4d6dfdb8b802605396758f +size 7888373 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png index 3dc99e604..29134afca 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:291f2033a7b4cfc10fb3301283c167b3fbc288bc173c95b21bc726bf076865af -size 7649213 +oid sha256:81bb1f05a6f72aebee41941b2390ca0a35c4368758af2d27b3e81bbee8901c56 +size 7956102 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png index 331b8b30a..1a052e444 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png +++ b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a68aa183691be3240e881057cb2f5785e50228c9c5dd98163ba766b5cafa8b55 -size 88601 +oid sha256:1f312445263030ea79e2d1a05c62d19ade816c9cda9f0d3494ab76fde1e03ace +size 92534 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png index c375fff78..769e172b7 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png +++ b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53a7bc1aa7279cce0b73568b6ed9b141377f7654b73dcf926c43c22dd908039a -size 88502 +oid sha256:672f167d514f01c625145701ea0d45ced503a6e84cae75fbdb67e6b0db179aee +size 92318 From ac0adfccac9e2d4028c565d339f686b02cf6c9aa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Apr 2026 22:11:53 +1000 Subject: [PATCH 156/240] Optimize Block8x8F ScaledCopy for common scales --- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 324 +++++++++++++++++- .../Formats/Jpeg/Components/Block8x8F.cs | 2 +- 2 files changed, 324 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index 179f9aa28..c2a95989d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -29,7 +29,50 @@ internal partial struct Block8x8F return; } - // TODO: Optimize: implement all cases with scale-specific, loopless code! + if (horizontalScale == 2 && verticalScale == 1) + { + this.CopyTo2x1Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 1 && verticalScale == 2) + { + this.CopyTo1x2Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 4 && verticalScale == 1) + { + this.CopyTo4x1Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 4 && verticalScale == 2) + { + this.CopyTo4x2Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 1 && verticalScale == 4) + { + this.CopyTo1x4Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 2 && verticalScale == 4) + { + this.CopyTo2x4Scale(ref areaOrigin, (uint)areaStride); + return; + } + + if (horizontalScale == 4 && verticalScale == 4) + { + this.CopyTo4x4Scale(ref areaOrigin, (uint)areaStride); + return; + } + + // The common 1x, 2x, and 4x integral scales are specialized above. + // Uncommon legal factor-3 scales use the generic fallback. this.CopyArbitraryScale(ref areaOrigin, (uint)areaStride, (uint)horizontalScale, (uint)verticalScale); } @@ -85,6 +128,285 @@ internal partial struct Block8x8F } } + /// + /// Copies the full 8x8 block into the destination buffer while doubling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo2x1Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + WidenRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 2u, 2u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 3u, 3u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 4u, 4u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 5u, 5u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 6u, 6u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 7u, 7u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while doubling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo1x2Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 4u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 5u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 6u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 7u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 8u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 9u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 10u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 11u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 12u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 13u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 14u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 15u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while quadrupling only the horizontal axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo4x1Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 1u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 2u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 3u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 4u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 5u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 6u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 7u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while quadrupling horizontally and doubling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo4x2Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 2u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 3u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 4u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 5u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 6u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 7u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 8u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 9u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 10u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 11u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 12u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 13u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 14u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 15u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while quadrupling only the vertical axis. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo1x4Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 16u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 17u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 18u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 4u, 19u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 20u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 21u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 22u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 5u, 23u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 24u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 25u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 26u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 6u, 27u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 28u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 29u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 30u, areaStride); + CopyRow8(ref sourceBase, ref areaOrigin, 7u, 31u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while doubling horizontally and quadrupling vertically. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo2x4Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + WidenRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 4u, 16u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 4u, 17u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 4u, 18u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 4u, 19u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 5u, 20u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 5u, 21u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 5u, 22u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 5u, 23u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 6u, 24u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 6u, 25u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 6u, 26u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 6u, 27u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 7u, 28u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 7u, 29u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 7u, 30u, areaStride); + WidenRow8(ref sourceBase, ref areaOrigin, 7u, 31u, areaStride); + } + + /// + /// Copies the full 8x8 block into the destination buffer while quadrupling both axes. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void CopyTo4x4Scale(ref float areaOrigin, uint areaStride) + { + ref Vector4 sourceBase = ref this.V0L; + + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 0u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 1u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 2u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 0u, 3u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 4u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 5u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 6u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 1u, 7u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 8u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 9u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 10u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 2u, 11u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 12u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 13u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 14u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 3u, 15u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 16u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 17u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 18u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 4u, 19u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 20u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 21u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 22u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 5u, 23u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 24u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 25u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 26u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 6u, 27u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 28u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 29u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 30u, areaStride); + ExpandRow8(ref sourceBase, ref areaOrigin, 7u, 31u, areaStride); + } + + /// + /// Copies one eight-sample row from the full block to the destination row. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyRow8(ref Vector4 sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref Vector4 source = ref Unsafe.Add(ref sourceBase, sourceRow * 2u); + ref Vector4 dest = ref Unsafe.As(ref Unsafe.Add(ref areaOrigin, destRow * areaStride)); + + dest = source; + Unsafe.Add(ref dest, 1u) = Unsafe.Add(ref source, 1u); + } + + /// + /// Expands one eight-sample row to sixteen samples by duplicating each source value horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void WidenRow8(ref Vector4 sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref Vector4 sourceLeft = ref Unsafe.Add(ref sourceBase, sourceRow * 2u); + ref Vector4 sourceRight = ref Unsafe.Add(ref sourceLeft, 1u); + ref Vector4 dest = ref Unsafe.As(ref Unsafe.Add(ref areaOrigin, destRow * areaStride)); + + Vector4 xyLeft = new(sourceLeft.X); + xyLeft.Z = sourceLeft.Y; + xyLeft.W = sourceLeft.Y; + + Vector4 zwLeft = new(sourceLeft.Z); + zwLeft.Z = sourceLeft.W; + zwLeft.W = sourceLeft.W; + + Vector4 xyRight = new(sourceRight.X); + xyRight.Z = sourceRight.Y; + xyRight.W = sourceRight.Y; + + Vector4 zwRight = new(sourceRight.Z); + zwRight.Z = sourceRight.W; + zwRight.W = sourceRight.W; + + dest = xyLeft; + Unsafe.Add(ref dest, 1u) = zwLeft; + Unsafe.Add(ref dest, 2u) = xyRight; + Unsafe.Add(ref dest, 3u) = zwRight; + } + + /// + /// Expands one eight-sample row to thirty-two samples by duplicating each source value four times horizontally. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExpandRow8(ref Vector4 sourceBase, ref float areaOrigin, nuint sourceRow, nuint destRow, uint areaStride) + { + ref Vector4 sourceLeft = ref Unsafe.Add(ref sourceBase, sourceRow * 2u); + ref Vector4 sourceRight = ref Unsafe.Add(ref sourceLeft, 1u); + ref Vector4 dest = ref Unsafe.As(ref Unsafe.Add(ref areaOrigin, destRow * areaStride)); + + dest = new Vector4(sourceLeft.X); + Unsafe.Add(ref dest, 1u) = new Vector4(sourceLeft.Y); + Unsafe.Add(ref dest, 2u) = new Vector4(sourceLeft.Z); + Unsafe.Add(ref dest, 3u) = new Vector4(sourceLeft.W); + Unsafe.Add(ref dest, 4u) = new Vector4(sourceRight.X); + Unsafe.Add(ref dest, 5u) = new Vector4(sourceRight.Y); + Unsafe.Add(ref dest, 6u) = new Vector4(sourceRight.Z); + Unsafe.Add(ref dest, 7u) = new Vector4(sourceRight.W); + } + [MethodImpl(InliningOptions.ColdPath)] private void CopyArbitraryScale(ref float areaOrigin, uint areaStride, uint horizontalScale, uint verticalScale) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 19a695d07..f3767c888 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -132,7 +132,7 @@ internal partial struct Block8x8F : IEquatable /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(float[] dest) + public readonly void ScaledCopyTo(float[] dest) { DebugGuard.MustBeGreaterThanOrEqualTo(dest.Length, Size, "dest is too small"); From 3b1b8956cf3733d098b84679e5536ae2eb024ca0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 18 Apr 2026 21:40:27 +1000 Subject: [PATCH 157/240] Stop parsing after SOS in JPEG decoder in ProcessStartOfFrameMarker --- .../Formats/Jpeg/JpegDecoderCore.cs | 30 +++++++++++++++---- .../Formats/Jpg/JpegDecoderTests.cs | 10 +++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Jpg/issues/issue3118-multiple-sof.jpg | 3 ++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index d4517e9f1..d503323d2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -385,7 +385,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly); + if (!this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly)) + { + return; + } + break; case JpegConstants.Markers.SOF9: @@ -398,7 +402,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.scanDecoder.ResetInterval = this.resetInterval.Value; } - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly); + if (!this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly)) + { + return; + } + break; case JpegConstants.Markers.SOF5: @@ -429,7 +437,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData } // It's highly unlikely that APPn related data will be found after the SOS marker - // We should have gathered everything we need by now. + // So we can stop parsing here and return the metadata we have parsed so far, instead + // of trying to parse any APPn markers after the SOS marker and risking running out of + // memory or other exceptions. return; case JpegConstants.Markers.DHT: @@ -1212,13 +1222,19 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData /// The current frame marker. /// The jpeg decoding component type. /// Whether to parse metadata only. - private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) + private bool ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) { if (this.Frame != null) { - if (metadataOnly) + // If we have found the SOS marker, we can stop parsing as we have all + // the information we need to decode the image. + // It's possible that there are APPn related markers after the SOS marker, + // but it's highly unlikely and we would be better off stopping parsing + // and decoding the image instead of trying to parse those APPn markers + // and risking running out of memory or other exceptions. + if (this.hasSOSMarker) { - return; + return false; } JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); @@ -1351,6 +1367,8 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.Frame.Init(maxH, maxV); this.scanDecoder.InjectFrameData(this.Frame, this); } + + return true; } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 36847536b..d7d81bdfd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -457,4 +457,14 @@ public partial class JpegDecoderTests byte[] data = [0xFF, 0xD8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; using Image image = Image.Load(data); }); + + // https://github.com/SixLabors/ImageSharp/issues/3118 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue3118, PixelTypes.Rgb24)] + public void Issue3118_Multiple_SOF_WithSOS_DoesNotThrow(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder.Instance); + image.DebugSave(provider); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f7e130d66..1a8cf948e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -361,6 +361,7 @@ public static class TestImages public const string Issue2758 = "Jpg/issues/issue-2758.jpg"; public const string Issue2857 = "Jpg/issues/issue-2857-subsub-ifds.jpg"; public const string Issue2948 = "Jpg/issues/issue-2948-sos.jpg"; + public const string Issue3118 = "Jpg/issues/issue3118-multiple-sof.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg new file mode 100644 index 000000000..e8897a511 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d9073bc60cb8f5965993654ec5a949cdbacebe6365db9831ece860095ca85f +size 11541050 From 66671a0e9f348dda707af5d2fbba3712c5ed173f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 19 Apr 2026 02:28:44 +1000 Subject: [PATCH 158/240] Implement proper segment integrity handling & tolerant parsing --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 33 +++- src/ImageSharp/Formats/DecoderOptions.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 163 ++++++++++++++---- .../Sections/GifXmpApplicationExtension.cs | 17 +- src/ImageSharp/Formats/ImageDecoderCore.cs | 66 +++++++ .../Formats/Jpeg/JpegDecoderCore.cs | 104 +++++++++-- src/ImageSharp/Formats/Png/PngChunk.cs | 10 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 101 ++++++++--- .../Formats/SegmentIntegrityHandling.cs | 22 +-- .../Formats/Tiff/TiffDecoderCore.cs | 14 +- .../Formats/Webp/WebpAnimationDecoder.cs | 45 +++-- .../Formats/Webp/WebpChunkParsingUtils.cs | 102 ++--------- .../Formats/Webp/WebpDecoderCore.cs | 22 ++- .../IccChromaticityTagDataEntry.cs | 2 +- .../Formats/Bmp/BmpMetadataTests.cs | 41 ++++- .../Formats/Gif/GifDecoderTests.cs | 50 ++++++ .../Formats/Jpg/JpegDecoderTests.cs | 57 ++++++ .../Formats/Png/PngDecoderTests.cs | 6 +- .../Formats/Tiff/TiffDecoderTests.cs | 38 ++++ .../Formats/Tiff/TiffMetadataTests.cs | 4 +- .../Formats/WebP/WebpMetaDataTests.cs | 28 +++ .../CorruptedMetadataImageFactory.cs | 32 ++++ 22 files changed, 726 insertions(+), 233 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 230526fd4..ab96aeaa5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1431,12 +1431,8 @@ internal sealed class BmpDecoderCore : ImageDecoderCore this.infoHeader = BmpInfoHeader.ParseV5(buffer); if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0) { - // Read color profile. long streamPosition = stream.Position; - byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; - stream.Position = infoHeaderStart + this.infoHeader.ProfileData; - stream.Read(iccProfileData); - this.metadata.IccProfile = new IccProfile(iccProfileData); + this.ExecuteAncillarySegmentAction(() => this.ReadIccProfile(stream, this.metadata, infoHeaderStart)); stream.Position = streamPosition; } } @@ -1470,6 +1466,33 @@ internal sealed class BmpDecoderCore : ImageDecoderCore this.Dimensions = new Size(this.infoHeader.Width, this.infoHeader.Height); } + /// + /// Reads the embedded ICC profile from the BMP V5 info header. + /// + /// The containing image data. + /// The image metadata. + /// The stream position where the info header begins. + private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, long infoHeaderStart) + { + byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; + stream.Position = infoHeaderStart + this.infoHeader.ProfileData; + + if (stream.Read(iccProfileData) != iccProfileData.Length) + { + BmpThrowHelper.ThrowInvalidImageContentException("Not enough data to read BMP ICC profile."); + } + + IccProfile profile = new(iccProfileData); + if (profile.CheckIsValid()) + { + metadata.IccProfile = profile; + } + else + { + throw new InvalidIccProfileException("Invalid BMP ICC profile."); + } + } + /// /// Reads the from the stream. /// diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index d4d80fc12..916888af3 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -60,7 +60,7 @@ public sealed class DecoderOptions /// /// Gets the segment error handling strategy to use during decoding. /// - public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreNonCritical; + public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreAncillary; /// /// Gets a value that controls how ICC profiles are handled during decode. diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3d32c7cda..c087b6681 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -146,10 +146,10 @@ internal sealed class GifDecoderCore : ImageDecoderCore this.ReadGraphicalControlExtension(stream); break; case GifConstants.CommentLabel: - this.ReadComments(stream); + this.ExecuteAncillarySegmentAction(() => this.ReadComments(stream)); break; case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(stream); + this.ExecuteAncillarySegmentAction(() => this.ReadApplicationExtension(stream)); break; case GifConstants.PlainTextLabel: SkipBlock(stream); // Not supported by any known decoder. @@ -226,10 +226,10 @@ internal sealed class GifDecoderCore : ImageDecoderCore this.ReadGraphicalControlExtension(stream); break; case GifConstants.CommentLabel: - this.ReadComments(stream); + this.ExecuteAncillarySegmentAction(() => this.ReadComments(stream)); break; case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(stream); + this.ExecuteAncillarySegmentAction(() => this.ReadApplicationExtension(stream)); break; case GifConstants.PlainTextLabel: SkipBlock(stream); // Not supported by any known decoder. @@ -266,6 +266,13 @@ internal sealed class GifDecoderCore : ImageDecoderCore GifThrowHelper.ThrowNoHeader(); } + // Ignoring a malformed ancillary extension must not let identify succeed for a file + // that never contained any readable image frame data. + if (previousFrame is null) + { + GifThrowHelper.ThrowNoData(); + } + return new ImageInfo( new Size(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height), this.metadata, @@ -331,51 +338,128 @@ internal sealed class GifDecoderCore : ImageDecoderCore private void ReadApplicationExtension(BufferedReadStream stream) { int appLength = stream.ReadByte(); + if (appLength == -1) + { + GifThrowHelper.ThrowInvalidImageContentException("Unexpected end of stream while reading gif application extension"); + } + + if (appLength != GifConstants.ApplicationBlockSize) + { + this.ThrowOrIgnoreNonStrictSegmentError($"Gif application extension length '{appLength}' is invalid"); + SkipBlock(stream, appLength); + return; + } // If the length is 11 then it's a valid extension and most likely // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this. long position = stream.Position; - if (appLength == GifConstants.ApplicationBlockSize) + int bytesRead = stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize); + if (bytesRead != GifConstants.ApplicationBlockSize) { - stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize); - bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes); - if (isXmp && !this.skipMetadata) - { - GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); - if (extension.Data.Length > 0) - { - this.metadata!.XmpProfile = new XmpProfile(extension.Data); - } - else - { - // Reset the stream position and continue. - stream.Position = position; - SkipBlock(stream, appLength); - } + GifThrowHelper.ThrowInvalidImageContentException("Unexpected end of stream while reading gif application extension"); + } - return; - } + bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes); + if (isXmp) + { + this.ReadXmpApplicationExtension(stream, position, appLength); + return; + } - int subBlockSize = stream.ReadByte(); + int subBlockSize = stream.ReadByte(); + if (subBlockSize == -1) + { + GifThrowHelper.ThrowInvalidImageContentException("Unexpected end of stream while reading gif application extension"); + } - // TODO: There's also a NETSCAPE buffer extension. - // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension - if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) - { - stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span[1..]).RepeatCount; - stream.Skip(1); // Skip the terminator. - return; - } + // TODO: There's also a NETSCAPE buffer extension. + // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension + if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) + { + this.ReadNetscapeApplicationExtension(stream); + return; + } + + // Could be something else not supported yet. + // Skip the subblock and terminator. + SkipBlock(stream, subBlockSize); + } + + /// + /// Reads the GIF XMP application extension. + /// + /// The containing image data. + /// The stream position where the application identifier begins. + /// The application block length. + private void ReadXmpApplicationExtension(BufferedReadStream stream, long applicationPosition, int appLength) + { + if (this.skipMetadata) + { + stream.Position = applicationPosition; + SkipBlock(stream, appLength); + return; + } - // Could be something else not supported yet. - // Skip the subblock and terminator. - SkipBlock(stream, subBlockSize); + bool completed = false; + this.ExecuteAncillarySegmentAction( + () => + { + this.ReadXmpApplicationExtensionData(stream, applicationPosition, appLength); + completed = true; + }); + + if (!completed) + { + stream.Position = applicationPosition; + SkipBlock(stream, appLength); + } + } + /// + /// Reads the GIF XMP application extension data. + /// + /// The containing image data. + /// The stream position where the application identifier begins. + /// The application block length. + private void ReadXmpApplicationExtensionData(BufferedReadStream stream, long applicationPosition, int appLength) + { + GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); + if (extension.Data.Length > 0) + { + this.metadata!.XmpProfile = new XmpProfile(extension.Data); return; } - SkipBlock(stream, appLength); // Not supported by any known decoder. + stream.Position = applicationPosition; + SkipBlock(stream, appLength); + } + + /// + /// Reads the GIF NETSCAPE looping application extension. + /// + /// The containing image data. + private void ReadNetscapeApplicationExtension(BufferedReadStream stream) => + this.ExecuteAncillarySegmentAction(() => this.ReadNetscapeApplicationExtensionData(stream)); + + /// + /// Reads the GIF NETSCAPE looping application extension data. + /// + /// The containing image data. + private void ReadNetscapeApplicationExtensionData(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize); + if (bytesRead != GifConstants.NetscapeLoopingSubBlockSize) + { + throw new InvalidImageContentException("Unexpected end of stream while reading gif application extension"); + } + + this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span[1..]).RepeatCount; + + int terminator = stream.ReadByte(); + if (terminator == -1) + { + throw new InvalidImageContentException("Unexpected end of stream while reading gif application extension"); + } } /// @@ -428,7 +512,12 @@ internal sealed class GifDecoderCore : ImageDecoderCore using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); Span commentsSpan = commentsBuffer.GetSpan(); - stream.Read(commentsSpan); + int bytesRead = stream.Read(commentsSpan); + if (bytesRead != length) + { + GifThrowHelper.ThrowInvalidImageContentException("Unexpected end of stream while reading gif comment"); + } + string commentPart = GifConstants.Encoding.GetString(commentsSpan); stringBuilder.Append(commentPart); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs index dc78f28bf..85c4959a9 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs @@ -30,7 +30,11 @@ internal readonly struct GifXmpApplicationExtension : IGifExtension /// The XMP metadata public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator) { - byte[] xmpBytes = ReadXmpData(stream, allocator); + byte[] xmpBytes = ReadXmpData(stream, allocator, out bool terminated); + if (!terminated) + { + throw new InvalidImageContentException("Unexpected end of stream while reading gif XMP data"); + } // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0 @@ -71,7 +75,7 @@ internal readonly struct GifXmpApplicationExtension : IGifExtension return this.ContentLength; } - private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator) + private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator, out bool terminated) { using ChunkedMemoryStream bytes = new(allocator); @@ -83,8 +87,15 @@ internal readonly struct GifXmpApplicationExtension : IGifExtension while (true) { int b = stream.ReadByte(); - if (b <= 0) + if (b == 0) + { + terminated = true; + return bytes.ToArray(); + } + + if (b < 0) { + terminated = false; return bytes.ToArray(); } diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs index f6b1a92a0..f76f1cd7a 100644 --- a/src/ImageSharp/Formats/ImageDecoderCore.cs +++ b/src/ImageSharp/Formats/ImageDecoderCore.cs @@ -33,6 +33,72 @@ internal abstract class ImageDecoderCore /// public Size Dimensions { get; protected internal set; } + /// + /// Executes a known ancillary segment parsing action using the configured integrity policy. + /// + /// The action. + protected void ExecuteAncillarySegmentAction(Action action) + { + if (this.Options.SegmentIntegrityHandling is SegmentIntegrityHandling.Strict) + { + action(); + return; + } + + try + { + action(); + } + catch (Exception ex) when (ex + is ImageFormatException + or InvalidIccProfileException + or InvalidImageContentException + or InvalidOperationException + or NotSupportedException) + { + } + } + + /// + /// Executes a known image data segment parsing action using the configured integrity policy. + /// + /// The action. + protected void ExecuteImageDataSegmentAction(Action action) + { + if (this.Options.SegmentIntegrityHandling is not SegmentIntegrityHandling.IgnoreImageData) + { + action(); + return; + } + + try + { + action(); + } + catch (Exception ex) when (ex + is ImageFormatException + or InvalidIccProfileException + or InvalidImageContentException + or InvalidOperationException + or NotSupportedException) + { + } + } + + /// + /// Throws unless the decoder is running in a non-strict segment integrity mode. + /// Use this only from within when local control flow + /// must continue after the error. + /// + /// The exception message. + protected void ThrowOrIgnoreNonStrictSegmentError(string message) + { + if (this.Options.SegmentIntegrityHandling is SegmentIntegrityHandling.Strict) + { + throw new InvalidImageContentException(message); + } + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index d503323d2..ae26914e8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -208,11 +208,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData JpegThrowHelper.ThrowInvalidImageContentException("Missing SOS marker."); } - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); + this.InitializeMetadataProfiles(); _ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile); @@ -232,11 +228,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData JpegThrowHelper.ThrowInvalidImageContentException("Missing SOS marker."); } - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); + this.InitializeMetadataProfiles(); Size pixelSize = this.Frame.PixelSize; return new ImageInfo(new Size(pixelSize.Width, pixelSize.Height), this.Metadata); @@ -711,6 +703,10 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { this.Metadata.IccProfile = profile; } + else + { + throw new InvalidIccProfileException("Invalid ICC profile."); + } } } @@ -781,6 +777,18 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData return 0; } + /// + /// Initializes decoded metadata profiles using the configured ancillary segment handling policy. + /// + private void InitializeMetadataProfiles() + { + this.ExecuteAncillarySegmentAction(this.InitExifProfile); + this.ExecuteAncillarySegmentAction(this.InitIccProfile); + this.ExecuteAncillarySegmentAction(this.InitIptcProfile); + this.ExecuteAncillarySegmentAction(this.InitXmpProfile); + this.ExecuteAncillarySegmentAction(this.InitDerivedMetadataProperties); + } + /// /// Extends the profile with additional data. /// @@ -804,7 +812,16 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData // We can only decode JFif identifiers. // Some images contain multiple JFIF markers (Issue 1932) so we check to see // if it's already been read. - if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) + if (remaining < JFifMarker.Length) + { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App0 Marker length."); + + // Skip the application header length + stream.Skip(remaining); + return; + } + + if (!this.jFif.Equals(default)) { // Skip the application header length stream.Skip(remaining); @@ -814,7 +831,10 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData Span temp = stackalloc byte[2 * 16 * 4]; stream.Read(temp, 0, JFifMarker.Length); - _ = JFifMarker.TryParse(temp, out this.jFif); + if (!JFifMarker.TryParse(temp, out this.jFif)) + { + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App0 marker."); + } remaining -= JFifMarker.Length; @@ -823,7 +843,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { if (stream.Position + remaining >= stream.Length) { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App0 Marker length."); + this.ThrowOrIgnoreNonStrictSegmentError("Bad App0 Marker length."); + stream.Skip(remaining); + return; } stream.Skip(remaining); @@ -839,7 +861,16 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { const int exifMarkerLength = 6; const int xmpMarkerLength = 29; - if (remaining < exifMarkerLength || this.skipMetadata) + if (remaining < exifMarkerLength) + { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App1 Marker length."); + + // Skip the application header length. + stream.Skip(remaining); + return; + } + + if (this.skipMetadata) { // Skip the application header length. stream.Skip(remaining); @@ -848,7 +879,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData if (stream.Position + remaining >= stream.Length) { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); + this.ThrowOrIgnoreNonStrictSegmentError("Bad App1 Marker length."); + stream.Skip(remaining); + return; } Span temp = stackalloc byte[2 * 16 * 4]; @@ -879,8 +912,10 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData if (ProfileResolver.IsProfile(temp, ProfileResolver.XmpMarker[..exifMarkerLength])) { const int remainingXmpMarkerBytes = xmpMarkerLength - exifMarkerLength; - if (remaining < remainingXmpMarkerBytes || this.skipMetadata) + if (remaining < remainingXmpMarkerBytes) { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App1 Marker length."); + // Skip the application header length. stream.Skip(remaining); return; @@ -906,6 +941,10 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData remaining = 0; } + else + { + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App1 marker."); + } } // Skip over any remaining bytes of this header. @@ -921,7 +960,15 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { // Length is 14 though we only need to check 12. const int icclength = 14; - if (remaining < icclength || this.skipMetadata) + if (remaining < icclength) + { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App2 Marker length."); + + stream.Skip(remaining); + return; + } + + if (this.skipMetadata) { stream.Skip(remaining); return; @@ -962,7 +1009,15 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData /// The remaining bytes in the segment block. private void ProcessApp13Marker(BufferedReadStream stream, int remaining) { - if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata) + if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length) + { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App13 Marker length."); + + stream.Skip(remaining); + return; + } + + if (this.skipMetadata) { stream.Skip(remaining); return; @@ -980,6 +1035,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { if (!ProfileResolver.IsProfile(blockDataSpan[..4], ProfileResolver.AdobeImageResourceBlockMarker)) { + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App13 marker."); return; } @@ -996,6 +1052,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); break; } + + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App13 marker."); + return; } else { @@ -1005,6 +1064,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData if (blockDataSpan.Length < dataStartIdx + resourceDataSize) { // Not enough data or the resource data size is wrong. + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App13 marker."); break; } @@ -1099,6 +1159,8 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData const int markerLength = AdobeMarker.Length; if (remaining < markerLength) { + this.ThrowOrIgnoreNonStrictSegmentError("Bad App14 Marker length."); + // Skip the application header length stream.Skip(remaining); return; @@ -1113,6 +1175,10 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData { this.hasAdobeMarker = true; } + else + { + this.ThrowOrIgnoreNonStrictSegmentError("Invalid App14 marker."); + } if (remaining > 0) { @@ -1568,7 +1634,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables); } - this.InitIccProfile(); + this.ExecuteAncillarySegmentAction(this.InitIccProfile); _ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile); this.scanDecoder.ParseEntropyCodedData(selectorsCount, profile); } diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index 3883986d0..bbf1a88bf 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -43,11 +43,11 @@ internal readonly struct PngChunk /// /// The segment handling behavior. public bool IsCritical(SegmentIntegrityHandling handling) - => handling switch + => this.Type switch { - SegmentIntegrityHandling.IgnoreNone => true, - SegmentIntegrityHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData, - SegmentIntegrityHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette, - _ => false, + PngChunkType.Header => true, + PngChunkType.Palette => true, + PngChunkType.Data or PngChunkType.FrameData => handling < SegmentIntegrityHandling.IgnoreImageData, + _ => handling < SegmentIntegrityHandling.IgnoreAncillary, }; } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d794c66e2..5b9eee116 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -800,18 +800,46 @@ internal sealed class PngDecoderCore : ImageDecoderCore PngMetadata pngMetadata, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel + { + using IMemoryOwner? blendMemory = frameControl.BlendMode == FrameBlendMode.Over + ? this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean) + : null; + + this.ExecuteImageDataSegmentAction(() => this.DecodePixelDataCore( + frameControl, + compressedStream, + imageFrame, + pngMetadata, + blendMemory, + cancellationToken)); + + this.hasImageData = true; + } + + /// + /// Decodes the raw pixel data row by row. + /// + /// The pixel format. + /// The frame control. + /// The compressed pixel data stream. + /// The image frame to decode to. + /// The png metadata. + /// The optional row blending buffer. + /// The cancellation token. + private void DecodePixelDataCore( + FrameControl frameControl, + DeflateStream compressedStream, + ImageFrame imageFrame, + PngMetadata pngMetadata, + IMemoryOwner? blendMemory, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { int currentRow = (int)frameControl.YOffset; int currentRowBytesRead = 0; int height = (int)frameControl.YMax; - IMemoryOwner? blendMemory = null; - Span blendRowBuffer = []; - if (frameControl.BlendMode == FrameBlendMode.Over) - { - blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); - blendRowBuffer = blendMemory.Memory.Span; - } + Span blendRowBuffer = blendMemory is null ? [] : blendMemory.Memory.Span; while (currentRow < height) { @@ -855,11 +883,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; default: - if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) - { - goto EXIT; - } - PngThrowHelper.ThrowUnknownFilter(); break; } @@ -870,8 +893,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore } EXIT: - this.hasImageData = true; - blendMemory?.Dispose(); + return; } /// @@ -890,6 +912,41 @@ internal sealed class PngDecoderCore : ImageDecoderCore PngMetadata pngMetadata, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel + { + using IMemoryOwner? blendMemory = frameControl.BlendMode == FrameBlendMode.Over + ? this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean) + : null; + + FrameControl frameControlCopy = frameControl; + this.ExecuteImageDataSegmentAction(() => this.DecodeInterlacedPixelDataCore( + frameControlCopy, + compressedStream, + imageFrame, + pngMetadata, + blendMemory, + cancellationToken)); + + this.hasImageData = true; + } + + /// + /// Decodes the raw interlaced pixel data row by row. + /// + /// The pixel format. + /// The frame control. + /// The compressed pixel data stream. + /// The current image frame. + /// The png metadata. + /// The optional row blending buffer. + /// The cancellation token. + private void DecodeInterlacedPixelDataCore( + FrameControl frameControl, + DeflateStream compressedStream, + ImageFrame imageFrame, + PngMetadata pngMetadata, + IMemoryOwner? blendMemory, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { int currentRow = Adam7.FirstRow[0] + (int)frameControl.YOffset; int currentRowBytesRead = 0; @@ -899,13 +956,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore Buffer2D imageBuffer = imageFrame.PixelBuffer; - IMemoryOwner? blendMemory = null; - Span blendRowBuffer = []; - if (frameControl.BlendMode == FrameBlendMode.Over) - { - blendMemory = this.memoryAllocator.Allocate(imageFrame.Width, AllocationOptions.Clean); - blendRowBuffer = blendMemory.Memory.Span; - } + Span blendRowBuffer = blendMemory is null ? [] : blendMemory.Memory.Span; while (true) { @@ -962,11 +1013,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; default: - if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) - { - goto EXIT; - } - PngThrowHelper.ThrowUnknownFilter(); break; } @@ -1002,8 +1048,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore } EXIT: - this.hasImageData = true; - blendMemory?.Dispose(); + return; } /// diff --git a/src/ImageSharp/Formats/SegmentIntegrityHandling.cs b/src/ImageSharp/Formats/SegmentIntegrityHandling.cs index 977aee4ad..f16c1bb26 100644 --- a/src/ImageSharp/Formats/SegmentIntegrityHandling.cs +++ b/src/ImageSharp/Formats/SegmentIntegrityHandling.cs @@ -1,30 +1,26 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats; /// -/// Specifies how to handle validation of errors in different segments of encoded image files. +/// Specifies how to handle validation of recoverable errors in ancillary and image data segments. +/// Structural errors that prevent safe decoding remain fatal regardless of the selected mode. /// public enum SegmentIntegrityHandling { /// - /// Do not ignore any errors. + /// Do not ignore any recoverable ancillary or image data segment errors. /// - IgnoreNone, + Strict = 0, /// - /// Ignore errors in non-critical segments of the encoded image. + /// Ignore recoverable errors in ancillary segments, such as optional metadata. /// - IgnoreNonCritical, + IgnoreAncillary = 1, /// - /// Ignore errors in data segments (e.g., image data, metadata). + /// Ignore recoverable errors in image data segments in addition to ancillary segments. /// - IgnoreData, - - /// - /// Ignore errors in all segments. - /// - IgnoreAll + IgnoreImageData = 2, } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 1822cb8d1..e232045be 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -289,7 +289,19 @@ internal class TiffDecoderCore : ImageDecoderCore // We resolve the ICC profile early so that we can use it for color conversion if needed. if (tags.TryGetValue(ExifTag.IccProfile, out IExifValue iccProfileBytes)) { - imageFrameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); + this.ExecuteAncillarySegmentAction( + () => + { + IccProfile profile = new(iccProfileBytes.Value); + if (profile.CheckIsValid()) + { + imageFrameMetaData.IccProfile = profile; + } + else + { + throw new InvalidIccProfileException("Invalid TIFF ICC profile."); + } + }); } } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 71e609fbc..c8044b9b7 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -64,9 +64,9 @@ internal class WebpAnimationDecoder : IDisposable private readonly BackgroundColorHandling backgroundColorHandling; /// - /// How to handle validation of errors in different segments of encoded image files. + /// Executes a known ancillary segment parsing action using the configured integrity policy. /// - private readonly SegmentIntegrityHandling segmentIntegrityHandling; + private readonly Action executeAncillarySegmentAction; /// /// Initializes a new instance of the class. @@ -76,21 +76,21 @@ internal class WebpAnimationDecoder : IDisposable /// The maximum number of frames to decode. Inclusive. /// Whether to skip metadata. /// The flag to decide how to handle the background color in the Animation Chunk. - /// How to handle validation of errors in different segments of encoded image files. + /// Executes a known ancillary segment parsing action using the configured integrity policy. public WebpAnimationDecoder( MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames, bool skipMetadata, BackgroundColorHandling backgroundColorHandling, - SegmentIntegrityHandling segmentIntegrityHandling) + Action executeAncillarySegmentAction) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.maxFrames = maxFrames; this.skipMetadata = skipMetadata; this.backgroundColorHandling = backgroundColorHandling; - this.segmentIntegrityHandling = segmentIntegrityHandling; + this.executeAncillarySegmentAction = executeAncillarySegmentAction; } /// @@ -118,7 +118,6 @@ internal class WebpAnimationDecoder : IDisposable : features.AnimationBackgroundColor!.Value; bool ignoreMetadata = this.skipMetadata; - SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling; Span buffer = stackalloc byte[4]; uint frameCount = 0; int remainingBytes = (int)completeDataSize; @@ -139,13 +138,7 @@ internal class WebpAnimationDecoder : IDisposable case WebpChunkType.Iccp: case WebpChunkType.Xmp: case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks( - stream, - chunkType, - this.metadata, - ignoreMetadata, - segmentIntegrityHandling, - buffer); + this.ReadOptionalChunk(stream, chunkType, this.metadata, ignoreMetadata); break; default: @@ -196,7 +189,6 @@ internal class WebpAnimationDecoder : IDisposable TPixel backgroundPixel = backgroundColor.ToPixel(); bool ignoreMetadata = this.skipMetadata; - SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling; Span buffer = stackalloc byte[4]; uint frameCount = 0; int remainingBytes = (int)completeDataSize; @@ -223,7 +215,7 @@ internal class WebpAnimationDecoder : IDisposable case WebpChunkType.Iccp: case WebpChunkType.Xmp: case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, ignoreMetadata, segmentIntegrityHandling, buffer); + this.ReadOptionalChunk(stream, chunkType, image!.Metadata, ignoreMetadata); break; default: @@ -380,6 +372,29 @@ internal class WebpAnimationDecoder : IDisposable frameMetadata.DisposalMode = frameData.DisposalMethod; } + private void ReadOptionalChunk( + BufferedReadStream stream, + WebpChunkType chunkType, + ImageMetadata metadata, + bool ignoreMetadata) + { + switch (chunkType) + { + case WebpChunkType.Iccp: + + // While ICC profiles are optional, an invalid ICC profile cannot be ignored because it must + // precede the frame data, and we cannot safely skip it without successfully reading its size. + WebpChunkParsingUtils.ReadIccProfile(stream, metadata, ignoreMetadata); + break; + case WebpChunkType.Exif: + this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata)); + break; + case WebpChunkType.Xmp: + this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata)); + break; + } + } + /// /// Reads the ALPH chunk data. /// diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index 26ae28fd4..119d53fe9 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -343,69 +343,18 @@ internal static class WebpChunkParsingUtils throw new ImageFormatException("Invalid Webp data, could not read chunk type."); } - /// - /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). - /// If there are more such chunks, readers MAY ignore all except the first one. - /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. - /// - /// The stream to read the data from. - /// The chunk type to parse. - /// The image metadata to write to. - /// If true, metadata will be ignored. - /// Indicates how to handle segment integrity issues. - /// Buffer to store the data read from the stream. - public static void ParseOptionalChunks( - BufferedReadStream stream, - WebpChunkType chunkType, - ImageMetadata metadata, - bool ignoreMetadata, - SegmentIntegrityHandling segmentIntegrityHandling, - Span buffer) - { - long streamLength = stream.Length; - while (stream.Position < streamLength) - { - switch (chunkType) - { - case WebpChunkType.Iccp: - ReadIccProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer); - break; - - case WebpChunkType.Exif: - ReadExifProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer); - break; - case WebpChunkType.Xmp: - ReadXmpProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer); - break; - default: - - // Ignore unknown chunks. - // These must always fall after the image data so we are safe to always skip them. - uint chunkLength = ReadChunkSize(stream, buffer, false); - stream.Skip((int)chunkLength); - break; - } - } - } - /// /// Reads the ICCP chunk from the stream. /// /// The stream to decode from. /// The image metadata. /// If true, metadata will be ignored. - /// Indicates how to handle segment integrity issues. - /// Temporary buffer. public static void ReadIccProfile( BufferedReadStream stream, ImageMetadata metadata, - bool ignoreMetadata, - SegmentIntegrityHandling segmentIntegrityHandling, - Span buffer) + bool ignoreMetadata) { - // While ICC profiles are optional, an invalid ICC profile cannot be ignored as it must precede the image data - // and since we canot determine its size to allow skipping without reading the chunk size, we have to throw if it's invalid. - // Hence we do not consider segment integrity handling here. + Span buffer = stackalloc byte[4]; uint iccpChunkSize = ReadChunkSize(stream, buffer); if (ignoreMetadata || metadata.IccProfile != null) { @@ -413,22 +362,20 @@ internal static class WebpChunkParsingUtils } else { - bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone; byte[] iccpData = new byte[iccpChunkSize]; int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize); - - // We have the size but the profile is invalid if we cannot read enough data. - // Use the segment integrity handling to determine if we throw. - if (bytesRead != iccpChunkSize && ignoreNone) + if (bytesRead != iccpChunkSize) { WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); } IccProfile profile = new(iccpData); - if (profile.CheckIsValid()) + if (!profile.CheckIsValid()) { - metadata.IccProfile = profile; + throw new InvalidIccProfileException("Invalid ICC profile."); } + + metadata.IccProfile = profile; } } @@ -438,17 +385,13 @@ internal static class WebpChunkParsingUtils /// The stream to decode from. /// The image metadata. /// If true, metadata will be ignored. - /// Indicates how to handle segment integrity issues. - /// Temporary buffer. public static void ReadExifProfile( BufferedReadStream stream, ImageMetadata metadata, - bool ignoreMetadata, - SegmentIntegrityHandling segmentIntegrityHandling, - Span buffer) + bool ignoreMetadata) { - bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone; - uint exifChunkSize = ReadChunkSize(stream, buffer, ignoreNone); + Span buffer = stackalloc byte[4]; + uint exifChunkSize = ReadChunkSize(stream, buffer); if (ignoreMetadata || metadata.ExifProfile != null) { stream.Skip((int)exifChunkSize); @@ -459,12 +402,7 @@ internal static class WebpChunkParsingUtils int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize); if (bytesRead != exifChunkSize) { - if (ignoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); - } - - return; + WebpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the EXIF profile"); } ExifProfile exifProfile = new(exifData); @@ -490,18 +428,13 @@ internal static class WebpChunkParsingUtils /// The stream to decode from. /// The image metadata. /// If true, metadata will be ignored. - /// Indicates how to handle segment integrity issues. - /// Temporary buffer. public static void ReadXmpProfile( BufferedReadStream stream, ImageMetadata metadata, - bool ignoreMetadata, - SegmentIntegrityHandling segmentIntegrityHandling, - Span buffer) + bool ignoreMetadata) { - bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone; - - uint xmpChunkSize = ReadChunkSize(stream, buffer, ignoreNone); + Span buffer = stackalloc byte[4]; + uint xmpChunkSize = ReadChunkSize(stream, buffer); if (ignoreMetadata || metadata.XmpProfile != null) { stream.Skip((int)xmpChunkSize); @@ -512,12 +445,7 @@ internal static class WebpChunkParsingUtils int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize); if (bytesRead != xmpChunkSize) { - if (ignoreNone) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); - } - - return; + WebpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the XMP profile"); } metadata.XmpProfile = new XmpProfile(xmpData); diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index d6a07eeca..55bdca69c 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -52,8 +52,6 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable /// private readonly BackgroundColorHandling backgroundColorHandling; - private readonly SegmentIntegrityHandling segmentIntegrityHandling; - /// /// Initializes a new instance of the class. /// @@ -62,7 +60,6 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable : base(options.GeneralOptions) { this.backgroundColorHandling = options.BackgroundColorHandling; - this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; this.configuration = options.GeneralOptions.Configuration; this.skipMetadata = options.GeneralOptions.SkipMetadata; this.maxFrames = options.GeneralOptions.MaxFrames; @@ -90,7 +87,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable this.maxFrames, this.skipMetadata, this.backgroundColorHandling, - this.segmentIntegrityHandling); + this.ExecuteAncillarySegmentAction); return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); } @@ -149,7 +146,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable this.maxFrames, this.skipMetadata, this.backgroundColorHandling, - this.segmentIntegrityHandling); + this.ExecuteAncillarySegmentAction); return animationDecoder.Identify( stream, @@ -278,19 +275,21 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable Span buffer) { bool ignoreMetadata = this.skipMetadata; - SegmentIntegrityHandling integrityHandling = this.segmentIntegrityHandling; switch (chunkType) { case WebpChunkType.Iccp: - WebpChunkParsingUtils.ReadIccProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer); + + // While ICC profiles are optional, an invalid ICC profile cannot be ignored because it must + // precede the image data, and we cannot safely skip it without successfully reading its size. + WebpChunkParsingUtils.ReadIccProfile(stream, metadata, ignoreMetadata); break; case WebpChunkType.Exif: - WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer); + this.ExecuteAncillarySegmentAction(() => WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata)); break; case WebpChunkType.Xmp: - WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer); + this.ExecuteAncillarySegmentAction(() => WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata)); break; case WebpChunkType.AnimationParameter: @@ -320,7 +319,6 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span buffer) { bool ignoreMetadata = this.skipMetadata; - SegmentIntegrityHandling integrityHandling = this.segmentIntegrityHandling; if (ignoreMetadata || (!features.ExifProfile && !features.XmpMetaData)) { @@ -334,11 +332,11 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null) { - WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer); + this.ExecuteAncillarySegmentAction(() => WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata)); } else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) { - WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer); + this.ExecuteAncillarySegmentAction(() => WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata)); } else { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs index 1938ad630..0f61857ba 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -140,7 +140,7 @@ internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable< [0.155, 0.070] ]; default: - throw new ArgumentException("Unrecognized colorant encoding"); + throw new InvalidIccProfileException("Unrecognized colorant encoding"); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 64564ae1d..bbfa4ac59 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming @@ -46,7 +49,7 @@ public class BmpMetadataTests } [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] + [WithFile(TestImages.Bmp.IccProfile, PixelTypes.Rgba32)] public void Decoder_CanReadColorProfile(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -56,4 +59,40 @@ public class BmpMetadataTests Assert.NotNull(metaData.IccProfile); Assert.Equal(16, metaData.IccProfile.Entries.Length); } + + [Fact] + public void Identify_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateBmpWithMalformedIccProfile()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateBmpWithMalformedIccProfile()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateBmpWithMalformedIccProfile())); + } + + [Fact] + public void Decode_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateBmpWithMalformedIccProfile()); + }); + } + + private static byte[] CreateBmpWithMalformedIccProfile() + => CorruptedMetadataImageFactory.CreateImageWithMalformedIccProfile(new BmpEncoder()); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index febc65da3..8a8b4866a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -408,4 +408,54 @@ public class GifDecoderTests image.DebugSaveMultiFrame(provider); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } + + [Fact] + public void Identify_MalformedApplicationExtension_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateGifWithMalformedApplicationExtension()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedApplicationExtension_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateGifWithMalformedApplicationExtension()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedApplicationExtension_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateGifWithMalformedApplicationExtension())); + } + + [Fact] + public void Decode_MalformedApplicationExtension_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateGifWithMalformedApplicationExtension()); + }); + } + + private static byte[] CreateGifWithMalformedApplicationExtension() + { + // The application extension declares a 5-byte application block (0x05), but GIF application + // extensions require the fixed identifier/authentication block to be 11 bytes long. + return + [ + (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a', + 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0x21, 0xFF, 0x05, (byte)'B', (byte)'a', (byte)'d', (byte)'A', (byte)'p', 0x00, + 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x02, 0x02, 0x44, 0x01, 0x00, + 0x3B + ]; + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d7d81bdfd..2f84a1a7b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -467,4 +467,61 @@ public partial class JpegDecoderTests using Image image = provider.GetImage(JpegDecoder.Instance); image.DebugSave(provider); } + + [Fact] + public void Identify_MalformedApp13Segment_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateJpegWithMalformedApp13Segment()); + Assert.True(info.Width > 0); + Assert.True(info.Height > 0); + } + + [Fact] + public void Decode_MalformedApp13Segment_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateJpegWithMalformedApp13Segment()); + Assert.True(image.Width > 0); + Assert.True(image.Height > 0); + } + + [Fact] + public void Identify_MalformedApp13Segment_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateJpegWithMalformedApp13Segment())); + } + + [Fact] + public void Decode_MalformedApp13Segment_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateJpegWithMalformedApp13Segment()); + }); + } + + private static byte[] CreateJpegWithMalformedApp13Segment() + { + byte[] source = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).Bytes; + + // This APP13 segment starts with the valid "Photoshop 3.0\0" identifier, but the remaining + // payload does not begin with the required "8BIM" image resource block signature. + byte[] malformedApp13 = + [ + 0xFF, 0xED, + 0x00, 0x1D, + (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', 0x00, + (byte)'B', (byte)'a', (byte)'d', (byte)'R', (byte)'e', (byte)'s', (byte)'o', (byte)'u', (byte)'r', (byte)'c', (byte)'e', (byte)'!', (byte)'!' + ]; + + byte[] payload = new byte[source.Length + malformedApp13.Length]; + payload[0] = source[0]; + payload[1] = source[1]; + + Buffer.BlockCopy(malformedApp13, 0, payload, 2, malformedApp13.Length); + Buffer.BlockCopy(source, 2, payload, 2 + malformedApp13.Length, source.Length - 2); + + return payload; + } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 4712fc0dd..803a12b03 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -405,7 +405,7 @@ public partial class PngDecoderTests TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }, stream); + ImageInfo imageInfo = Image.Identify(new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }, stream); Assert.NotNull(imageInfo); Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); @@ -594,7 +594,7 @@ public partial class PngDecoderTests public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors(TestImageProvider provider, bool compare) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }); + using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }); image.DebugSave(provider); if (compare) @@ -775,7 +775,7 @@ public partial class PngDecoderTests public void Binary_PrematureEof() { PngDecoder decoder = PngDecoder.Instance; - PngDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData } }; + PngDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData } }; using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options); // TODO: Try to reduce this to 1. diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index e432a7251..ab6f3122e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -6,7 +6,9 @@ using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; @@ -868,4 +870,40 @@ public class TiffDecoderTests : TiffDecoderBaseTester [WithFile(Issue2983, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_Issue2983(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Fact] + public void Identify_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateTiffWithMalformedIccProfile()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateTiffWithMalformedIccProfile()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateTiffWithMalformedIccProfile())); + } + + [Fact] + public void Decode_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateTiffWithMalformedIccProfile()); + }); + } + + private static byte[] CreateTiffWithMalformedIccProfile() + => CorruptedMetadataImageFactory.CreateImageWithMalformedIccProfile(new TiffEncoder()); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 8d75cd9bc..94ed54326 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestDataIcc; using SixLabors.ImageSharp.Tests.TestUtilities; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; @@ -335,8 +336,7 @@ public class TiffMetadataTests iptcProfile.SetValue(IptcTag.Name, "Test name"); rootFrameInput.Metadata.IptcProfile = iptcProfile; - IccProfileHeader iccProfileHeader = new() { Class = IccProfileClass.ColorSpace }; - IccProfile iccProfile = new(); + IccProfile iccProfile = new(IccTestDataProfiles.ProfileRandomArray); rootFrameInput.Metadata.IccProfile = iccProfile; TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 020b42f37..394479f89 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -200,4 +200,32 @@ public class WebpMetaDataTests }); Assert.Null(ex); } + + [Fact] + public void Identify_InvalidExifChunk_IgnoresNonCriticalErrorsByDefault() + { + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + ImageInfo info = Image.Identify(stream); + Assert.True(info.Width > 0); + Assert.True(info.Height > 0); + } + + [Fact] + public void Identify_InvalidExifChunk_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + Assert.Throws(() => Image.Identify(options, stream)); + } + + [Fact] + public void Decode_InvalidExifChunk_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + Assert.Throws(() => + { + using Image image = Image.Load(options, stream); + }); + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs b/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs new file mode 100644 index 000000000..c9d18510d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestDataIcc; + +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +/// +/// Creates encoded images with malformed metadata payloads while keeping the surrounding container valid. +/// +internal static class CorruptedMetadataImageFactory +{ + /// + /// Creates an encoded single-pixel image with a malformed ICC profile. + /// + /// The encoder used to produce the image bytes. + /// The encoded image bytes with a malformed ICC profile payload. + public static byte[] CreateImageWithMalformedIccProfile(IImageEncoder encoder) + { + using Image image = new(1, 1); + image[0, 0] = new Rgba32(255, 0, 0, 255); + image.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.HeaderInvalidSizeSmallArray); + + using MemoryStream stream = new(); + image.Save(stream, encoder); + + return stream.ToArray(); + } +} From 9304b57022c57bb5abbf14b29d8cf3900f8c30a9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 19 Apr 2026 13:52:37 +1000 Subject: [PATCH 159/240] Use ColdPath for Block8x8F --- .../Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs | 2 +- .../DownScalingComponentProcessor8.cs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index c2a95989d..160e6a4f9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -14,7 +14,7 @@ internal partial struct Block8x8F public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(InliningOptions.ColdPath)] public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) { if (horizontalScale == 1 && verticalScale == 1) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs index ef17bf002..0d881977f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs @@ -111,6 +111,14 @@ internal sealed class DownScalingComponentProcessor8 : ComponentProcessor return; } + // The common 1x, 2x, and 4x integral scales are specialized above. + // Uncommon legal factor-3 scales use the generic fallback. + CopyArbitraryScale(value, ref destRef, destStrideWidth, horizontalScale, verticalScale); + } + + [MethodImpl(InliningOptions.ColdPath)] + private static float CopyArbitraryScale(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { // The common 1x, 2x, and 4x integral scales are specialized above. // Uncommon legal factor-3 scales use the generic fallback. for (nuint y = 0; y < (uint)verticalScale; y++) @@ -122,6 +130,8 @@ internal sealed class DownScalingComponentProcessor8 : ComponentProcessor destRef = ref Unsafe.Add(ref destRef, (uint)destStrideWidth); } + + return destRef; } /// From 4abf307ca696fac8d7e33df0c6462327d0f96ac7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 19 Apr 2026 18:32:48 +1000 Subject: [PATCH 160/240] Potential fix for pull request finding 'Poor error handling: empty catch block' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/ImageSharp/Formats/ImageDecoderCore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs index f76f1cd7a..23b517568 100644 --- a/src/ImageSharp/Formats/ImageDecoderCore.cs +++ b/src/ImageSharp/Formats/ImageDecoderCore.cs @@ -56,6 +56,7 @@ internal abstract class ImageDecoderCore or InvalidOperationException or NotSupportedException) { + // Intentionally ignored in non-strict segment integrity modes. } } @@ -82,6 +83,7 @@ internal abstract class ImageDecoderCore or InvalidOperationException or NotSupportedException) { + // Intentionally ignored when image data integrity handling is set to IgnoreImageData. } } From 67b9713ae0385aec88dab9d251e0c7b488512a5f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 20 Apr 2026 15:18:05 +1000 Subject: [PATCH 161/240] Rename params --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 6 +++--- src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index ab96aeaa5..519b0d536 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1470,9 +1470,9 @@ internal sealed class BmpDecoderCore : ImageDecoderCore /// Reads the embedded ICC profile from the BMP V5 info header. /// /// The containing image data. - /// The image metadata. + /// The image metadata. /// The stream position where the info header begins. - private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, long infoHeaderStart) + private void ReadIccProfile(BufferedReadStream stream, ImageMetadata imageMetadata, long infoHeaderStart) { byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; stream.Position = infoHeaderStart + this.infoHeader.ProfileData; @@ -1485,7 +1485,7 @@ internal sealed class BmpDecoderCore : ImageDecoderCore IccProfile profile = new(iccProfileData); if (profile.CheckIsValid()) { - metadata.IccProfile = profile; + imageMetadata.IccProfile = profile; } else { diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index c8044b9b7..8a8ad823d 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -375,7 +375,7 @@ internal class WebpAnimationDecoder : IDisposable private void ReadOptionalChunk( BufferedReadStream stream, WebpChunkType chunkType, - ImageMetadata metadata, + ImageMetadata imageMetadata, bool ignoreMetadata) { switch (chunkType) @@ -384,13 +384,13 @@ internal class WebpAnimationDecoder : IDisposable // While ICC profiles are optional, an invalid ICC profile cannot be ignored because it must // precede the frame data, and we cannot safely skip it without successfully reading its size. - WebpChunkParsingUtils.ReadIccProfile(stream, metadata, ignoreMetadata); + WebpChunkParsingUtils.ReadIccProfile(stream, imageMetadata, ignoreMetadata); break; case WebpChunkType.Exif: - this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata)); + this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadExifProfile(stream, imageMetadata, ignoreMetadata)); break; case WebpChunkType.Xmp: - this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata)); + this.executeAncillarySegmentAction(() => WebpChunkParsingUtils.ReadXmpProfile(stream, imageMetadata, ignoreMetadata)); break; } } From bace120221dbf9b8c1f1df8103428cf629821b72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:10:58 +0000 Subject: [PATCH 162/240] Bump NuGet/setup-nuget from 2 to 4 Bumps [NuGet/setup-nuget](https://github.com/nuget/setup-nuget) from 2 to 4. - [Release notes](https://github.com/nuget/setup-nuget/releases) - [Commits](https://github.com/nuget/setup-nuget/compare/v2...v4) --- updated-dependencies: - dependency-name: NuGet/setup-nuget dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ace6a4306..5151e672f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -154,7 +154,7 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache uses: actions/cache@v5 @@ -233,7 +233,7 @@ jobs: submodules: recursive - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache uses: actions/cache@v5 diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 16ae0317d..43e1fede2 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -56,7 +56,7 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache uses: actions/cache@v5 From c470a9a05a5f503b34b7daa61b18e317ade87075 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 22 Apr 2026 22:19:45 +1000 Subject: [PATCH 163/240] Revert "Merge pull request #3056 from SixLabors/js/accumulative-memory-limit" This reverts commit c5624b534e6e51b5bdeed1aae5b1d7c3a9c330ae, reversing changes made to b3f37932b6c79b8d7ad902f1eee09d240b7b3b4d. --- .../Memory/Allocators/MemoryAllocator.cs | 180 +----------------- .../Allocators/MemoryAllocatorOptions.cs | 30 +-- .../Allocators/SimpleGcMemoryAllocator.cs | 40 +--- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 58 +----- .../Allocators/UnmanagedMemoryAllocator.cs | 24 +-- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 8 +- .../MemoryGroup{T}.Consumed.cs | 16 +- .../MemoryGroup{T}.Owned.cs | 5 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 38 +--- .../SimpleGcMemoryAllocatorTests.cs | 44 ----- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 56 +----- .../MemoryGroupTests.Allocate.cs | 10 +- 12 files changed, 45 insertions(+), 464 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index d6badf928..8eaf0b6d6 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -12,8 +12,6 @@ namespace SixLabors.ImageSharp.Memory; public abstract class MemoryAllocator { private const int OneGigabyte = 1 << 30; - private long accumulativeAllocatedBytes; - private int trackingSuppressionCount; /// /// Gets the default platform-specific global instance that @@ -25,44 +23,9 @@ public abstract class MemoryAllocator /// public static MemoryAllocator Default { get; } = Create(); - /// - /// Gets the maximum number of bytes that can be allocated by a memory group. - /// - /// - /// The allocation limit is determined by the process architecture: 4 GB for 64-bit processes and - /// 1 GB for 32-bit processes. - /// - internal long MemoryGroupAllocationLimitBytes { get; private protected set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; - - /// - /// Gets the maximum allowed total allocation size, in bytes, for the current process. - /// - /// - /// Defaults to , effectively imposing no limit on total allocations. - /// This property can be set to enforce a cap on total memory usage across all allocations made through this allocator instance, providing - /// a safeguard against excessive memory consumption.
- /// When the cumulative size of active allocations exceeds this limit, an will be thrown to - /// prevent further allocations and signal that the limit has been breached. - ///
- internal long AccumulativeAllocationLimitBytes { get; private protected set; } = long.MaxValue; + internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; - /// - /// Gets the maximum size, in bytes, that can be allocated for a single buffer. - /// - /// - /// The single buffer allocation limit is set to 1 GB by default. - /// - internal int SingleBufferAllocationLimitBytes { get; private protected set; } = OneGigabyte; - - /// - /// Gets a value indicating whether change tracking is currently suppressed for this instance. - /// - /// - /// When change tracking is suppressed, modifications to the object will not be recorded or - /// trigger change notifications. This property is used internally to temporarily disable tracking during - /// batch updates or initialization. - /// - private bool IsTrackingSuppressed => Volatile.Read(ref this.trackingSuppressionCount) > 0; + internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. @@ -90,11 +53,6 @@ public abstract class MemoryAllocator allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes); } - if (options.AccumulativeAllocationLimitMegabytes.HasValue) - { - allocator.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; - } - return allocator; } @@ -114,10 +72,6 @@ public abstract class MemoryAllocator /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. /// - /// - /// This does not dispose active allocations; callers are responsible for disposing all - /// instances to release memory. - /// public virtual void ReleaseRetainedResources() { } @@ -148,137 +102,11 @@ public abstract class MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); } - long totalLengthInBytesLong = (long)totalLengthInBytes; - this.ReserveAllocation(totalLengthInBytesLong); - - using (this.SuppressTracking()) - { - try - { - MemoryGroup group = this.AllocateGroupCore(totalLength, totalLengthInBytesLong, bufferAlignment, options); - group.SetAllocationTracking(this, totalLengthInBytesLong); - return group; - } - catch - { - this.ReleaseAccumulatedBytes(totalLengthInBytesLong); - throw; - } - } + // Cast to long is safe because we already checked that the total length is within the limit. + return this.AllocateGroupCore(totalLength, (long)totalLengthInBytes, bufferAlignment, options); } internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) where T : struct => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); - - /// - /// Tracks the allocation of an instance after reserving bytes. - /// - /// Type of the data stored in the buffer. - /// The allocation to track. - /// The allocation size in bytes. - /// The tracked allocation. - protected IMemoryOwner TrackAllocation(IMemoryOwner owner, ulong lengthInBytes) - where T : struct - { - if (this.IsTrackingSuppressed || lengthInBytes == 0) - { - return owner; - } - - return new TrackingMemoryOwner(owner, this, (long)lengthInBytes); - } - - /// - /// Reserves accumulative allocation bytes before creating the underlying buffer. - /// - /// The number of bytes to reserve. - protected void ReserveAllocation(long lengthInBytes) - { - if (this.IsTrackingSuppressed || lengthInBytes <= 0) - { - return; - } - - long total = Interlocked.Add(ref this.accumulativeAllocatedBytes, lengthInBytes); - if (total > this.AccumulativeAllocationLimitBytes) - { - _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); - InvalidMemoryOperationException.ThrowAllocationOverLimitException((ulong)lengthInBytes, this.AccumulativeAllocationLimitBytes); - } - } - - /// - /// Releases accumulative allocation bytes previously tracked by this allocator. - /// - /// The number of bytes to release. - internal void ReleaseAccumulatedBytes(long lengthInBytes) - { - if (lengthInBytes <= 0) - { - return; - } - - _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); - } - - /// - /// Suppresses accumulative allocation tracking for the lifetime of the returned scope. - /// - /// An that restores tracking when disposed. - internal IDisposable SuppressTracking() => new TrackingSuppressionScope(this); - - /// - /// Temporarily suppresses accumulative allocation tracking within a scope. - /// - private sealed class TrackingSuppressionScope : IDisposable - { - private MemoryAllocator? allocator; - - public TrackingSuppressionScope(MemoryAllocator allocator) - { - this.allocator = allocator; - _ = Interlocked.Increment(ref allocator.trackingSuppressionCount); - } - - public void Dispose() - { - if (this.allocator != null) - { - _ = Interlocked.Decrement(ref this.allocator.trackingSuppressionCount); - this.allocator = null; - } - } - } - - /// - /// Wraps an to release accumulative tracking on dispose. - /// - private sealed class TrackingMemoryOwner : IMemoryOwner - where T : struct - { - private IMemoryOwner? owner; - private readonly MemoryAllocator allocator; - private readonly long lengthInBytes; - - public TrackingMemoryOwner(IMemoryOwner owner, MemoryAllocator allocator, long lengthInBytes) - { - this.owner = owner; - this.allocator = allocator; - this.lengthInBytes = lengthInBytes; - } - - public Memory Memory => this.owner?.Memory ?? Memory.Empty; - - public void Dispose() - { - // Ensure only one caller disposes the inner owner and releases the reservation. - IMemoryOwner? inner = Interlocked.Exchange(ref this.owner, null); - if (inner != null) - { - inner.Dispose(); - this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes); - } - } - } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index 3339203da..d9ba62c1e 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -10,7 +10,6 @@ public struct MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; private int? allocationLimitMegabytes; - private int? accumulativeAllocationLimitMegabytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -18,7 +17,7 @@ public struct MemoryAllocatorOptions /// public int? MaximumPoolSizeMegabytes { - readonly get => this.maximumPoolSizeMegabytes; + get => this.maximumPoolSizeMegabytes; set { if (value.HasValue) @@ -36,7 +35,7 @@ public struct MemoryAllocatorOptions ///
public int? AllocationLimitMegabytes { - readonly get => this.allocationLimitMegabytes; + get => this.allocationLimitMegabytes; set { if (value.HasValue) @@ -47,29 +46,4 @@ public struct MemoryAllocatorOptions this.allocationLimitMegabytes = value; } } - - /// - /// Gets or sets a value defining the maximum total size that can be allocated by the allocator in Megabytes. - /// means platform default: 2GB on 32-bit processes, 8GB on 64-bit processes. - /// - public int? AccumulativeAllocationLimitMegabytes - { - readonly get => this.accumulativeAllocationLimitMegabytes; - set - { - if (value.HasValue) - { - Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AccumulativeAllocationLimitMegabytes)); - if (this.AllocationLimitMegabytes.HasValue) - { - Guard.MustBeGreaterThanOrEqualTo( - value.Value, - this.AllocationLimitMegabytes.Value, - nameof(this.AccumulativeAllocationLimitMegabytes)); - } - } - - this.accumulativeAllocationLimitMegabytes = value; - } - } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 9cbb15713..675afe8b9 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,32 +12,6 @@ namespace SixLabors.ImageSharp.Memory; ///
public sealed class SimpleGcMemoryAllocator : MemoryAllocator { - /// - /// Initializes a new instance of the class with default limits. - /// - public SimpleGcMemoryAllocator() - : this(default) - { - } - - /// - /// Initializes a new instance of the class with custom limits. - /// - /// The to apply. - public SimpleGcMemoryAllocator(MemoryAllocatorOptions options) - { - if (options.AllocationLimitMegabytes.HasValue) - { - this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; - this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes); - } - - if (options.AccumulativeAllocationLimitMegabytes.HasValue) - { - this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; - } - } - /// protected internal override int GetBufferCapacityInBytes() => int.MaxValue; @@ -55,18 +29,6 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); } - long lengthInBytesLong = (long)lengthInBytes; - this.ReserveAllocation(lengthInBytesLong); - - try - { - IMemoryOwner buffer = new BasicArrayBuffer(new T[length]); - return this.TrackAllocation(buffer, lengthInBytes); - } - catch - { - this.ReleaseAccumulatedBytes(lengthInBytesLong); - throw; - } + return new BasicArrayBuffer(new T[length]); } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index e34860b37..d5cd329f1 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -92,24 +92,13 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes) { - long lengthInBytesLong = (long)lengthInBytes; - this.ReserveAllocation(lengthInBytesLong); - - try - { - SharedArrayPoolBuffer buffer = new(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return this.TrackAllocation(buffer, lengthInBytes); - } - catch + SharedArrayPoolBuffer buffer = new(length); + if (options.Has(AllocationOptions.Clean)) { - this.ReleaseAccumulatedBytes(lengthInBytesLong); - throw; + buffer.GetSpan().Clear(); } + + return buffer; } if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes) @@ -117,38 +106,12 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - long lengthInBytesLong = (long)lengthInBytes; - this.ReserveAllocation(lengthInBytesLong); - - try - { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return this.TrackAllocation(buffer, lengthInBytes); - } - catch - { - this.ReleaseAccumulatedBytes(lengthInBytesLong); - throw; - } + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); + return buffer; } } - long nonPooledLengthInBytesLong = (long)lengthInBytes; - this.ReserveAllocation(nonPooledLengthInBytesLong); - - try - { - using (this.nonPoolAllocator.SuppressTracking()) - { - IMemoryOwner nonPooled = this.nonPoolAllocator.Allocate(length, options); - return this.TrackAllocation(nonPooled, lengthInBytes); - } - } - catch - { - this.ReleaseAccumulatedBytes(nonPooledLengthInBytesLong); - throw; - } + return this.nonPoolAllocator.Allocate(length, options); } /// @@ -181,10 +144,7 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato return poolGroup; } - using (this.nonPoolAllocator.SuppressTracking()) - { - return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); - } + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); } public override void ReleaseRetainedResources() => this.pool.Release(); diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index 73cef8b5e..daf1a7992 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory; @@ -20,26 +19,13 @@ internal class UnmanagedMemoryAllocator : MemoryAllocator protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - where T : struct { - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - long lengthInBytesLong = (long)lengthInBytes; - this.ReserveAllocation(lengthInBytesLong); - - try + UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); + if (options.Has(AllocationOptions.Clean)) { - UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return this.TrackAllocation(buffer, lengthInBytes); - } - catch - { - this.ReleaseAccumulatedBytes(lengthInBytesLong); - throw; + buffer.GetSpan().Clear(); } + + return buffer; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 03f26aab0..7e9719ea7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -15,12 +15,12 @@ public interface IMemoryGroup : IReadOnlyList> /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. ///
- public int BufferLength { get; } + int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - public long TotalLength { get; } + long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. @@ -29,7 +29,7 @@ public interface IMemoryGroup : IReadOnlyList> /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces /// the image buffers internally. /// - public bool IsValid { get; } + bool IsValid { get; } /// /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current @@ -39,5 +39,5 @@ public interface IMemoryGroup : IReadOnlyList> /// implementation, which is still available when casting to one of the underlying interfaces. /// /// A new instance mapping the current values in use. - public new MemoryGroupEnumerator GetEnumerator(); + new MemoryGroupEnumerator GetEnumerator(); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 75e93ce7f..950e2a019 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -31,23 +31,23 @@ internal abstract partial class MemoryGroup /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() => new(this); + public override MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } /// IEnumerator> IEnumerable>.GetEnumerator() - + { /* The runtime sees the Array class as if it implemented the * type-generic collection interfaces explicitly, so here we * can just cast the source array to IList> (or to * an equivalent type), and invoke the generic GetEnumerator * method directly from that interface reference. This saves * having to create our own iterator block here. */ - => ((IList>)this.source).GetEnumerator(); - - public override void Dispose() - { - this.View.Invalidate(); - this.ReleaseAllocationTracking(); + return ((IList>)this.source).GetEnumerator(); } + + public override void Dispose() => this.View.Invalidate(); } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index be92272bb..af896ee0e 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -73,8 +73,8 @@ internal abstract partial class MemoryGroup result[i] = currentBuffer; } - ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[^1], sizeOfLastBuffer, options); - result[^1] = lastBuffer; + ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); + result[result.Length - 1] = lastBuffer; return result; } @@ -155,7 +155,6 @@ internal abstract partial class MemoryGroup } } - this.ReleaseAllocationTracking(); this.memoryOwners = null; this.IsValid = false; this.groupLifetimeGuard = null; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index aa0e18895..6dd99fcb0 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -22,9 +22,6 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable private static readonly int ElementSize = Unsafe.SizeOf(); private MemoryGroupSpanCache memoryGroupSpanCache; - private MemoryAllocator? trackingAllocator; - private long trackingLengthInBytes; - private int trackingReleased; private MemoryGroup(int bufferLength, long totalLength) { @@ -55,45 +52,16 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// public abstract MemoryGroupEnumerator GetEnumerator(); - /// - /// Configures allocation tracking by specifying the allocator and the length, in bytes, to be tracked. - /// - /// The memory allocator to use for tracking allocations. - /// The length, in bytes, of the memory region to track. Must be greater than or equal to zero. - /// - /// Intended for initialization; callers should avoid changing tracking state concurrently with disposal. - /// - internal void SetAllocationTracking(MemoryAllocator allocator, long lengthInBytes) - { - this.trackingAllocator = allocator; - this.trackingLengthInBytes = lengthInBytes; - } - - /// - /// Releases any resources or tracking information associated with allocation tracking for this instance. - /// - /// - /// This method is intended to be called when allocation tracking is no longer needed. It is safe - /// to call multiple times; subsequent calls after the first have no effect, even when called concurrently. - /// - internal void ReleaseAllocationTracking() - { - if (Interlocked.Exchange(ref this.trackingReleased, 1) == 0 && this.trackingAllocator != null) - { - this.trackingAllocator.ReleaseAccumulatedBytes(this.trackingLengthInBytes); - this.trackingAllocator = null; - } - } - /// IEnumerator> IEnumerable>.GetEnumerator() - + { /* This method is implemented in each derived class. * Implementing the method here as non-abstract and throwing, * then reimplementing it explicitly in each derived class, is * a workaround for the lack of support for abstract explicit * interface method implementations in C#. */ - => throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); + throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); + } /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index c33e6d107..0e791c5d9 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -48,50 +48,6 @@ public class SimpleGcMemoryAllocatorTests } } - [Fact] - public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() - { - SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions - { - AccumulativeAllocationLimitMegabytes = 1 - }); - const int oneMb = 1 << 20; - - // Reserve the full limit with a single owner. - IMemoryOwner b0 = allocator.Allocate(oneMb); - - // Additional allocation should exceed the limit while the owner is live. - Assert.Throws(() => allocator.Allocate(1)); - - // Disposing the owner releases the reservation. - b0.Dispose(); - - // Allocation should succeed after the reservation is released. - allocator.Allocate(oneMb).Dispose(); - } - - [Fact] - public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() - { - SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions - { - AccumulativeAllocationLimitMegabytes = 1 - }); - const int oneMb = 1 << 20; - - // Reserve the full limit with a single group. - MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); - - // Additional allocation should exceed the limit while the group is live. - Assert.Throws(() => allocator.AllocateGroup(1, 1024)); - - // Disposing the group releases the reservation. - g0.Dispose(); - - // Allocation should succeed after the reservation is released. - allocator.AllocateGroup(oneMb, 1024).Dispose(); - } - [StructLayout(LayoutKind.Explicit, Size = 512)] private struct BigStruct { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 06da3505e..1d185a0de 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -16,8 +16,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests { public class BufferTests1 : BufferTestSuite { - private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => - new( + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( sharedArrayPoolThresholdInBytes: 1024, poolBufferSizeInBytes: 2048, maxPoolSizeInBytes: 2048 * 4, @@ -31,8 +31,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests public class BufferTests2 : BufferTestSuite { - private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => - new( + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( sharedArrayPoolThresholdInBytes: 512, poolBufferSizeInBytes: 1024, maxPoolSizeInBytes: 1024 * 4, @@ -179,8 +179,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g1.Dispose(); // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); - IntPtr dummy2 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); + IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); @@ -433,50 +433,6 @@ public class UniformUnmanagedPoolMemoryAllocatorTests Assert.Throws(() => allocator.AllocateGroup(5 * oneMb, 1024)); } - [Fact] - public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() - { - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - AccumulativeAllocationLimitMegabytes = 1 - }); - const int oneMb = 1 << 20; - - // Reserve the full limit with a single owner. - IMemoryOwner b0 = allocator.Allocate(oneMb); - - // Additional allocation should exceed the limit while the owner is live. - Assert.Throws(() => allocator.Allocate(1)); - - // Disposing the owner releases the reservation. - b0.Dispose(); - - // Allocation should succeed after the reservation is released. - allocator.Allocate(oneMb).Dispose(); - } - - [Fact] - public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() - { - MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions - { - AccumulativeAllocationLimitMegabytes = 1 - }); - const int oneMb = 1 << 20; - - // Reserve the full limit with a single group. - MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); - - // Additional allocation should exceed the limit while the group is live. - Assert.Throws(() => allocator.AllocateGroup(1, 1024)); - - // Disposing the group releases the reservation. - g0.Dispose(); - - // Allocation should succeed after the reservation is released. - allocator.AllocateGroup(oneMb, 1024).Dispose(); - } - [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] public void MemoryAllocator_Create_SetHighLimit() { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index cca2230e6..678a089a8 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -98,15 +98,7 @@ public partial class MemoryGroupTests [InlineData(AllocationOptions.Clean)] public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) { - // Disable trimming to avoid buffers being freed between Return and TryAllocate by the - // trim timer or the Gen2 GC callback. - UniformUnmanagedMemoryPool pool = new( - 10, - 5, - new UniformUnmanagedMemoryPool.TrimSettings - { - Rate = 0 - }); + UniformUnmanagedMemoryPool pool = new(10, 5); UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { From 39b13a46c69e495966109dc09a9ebd652635560a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 23 Apr 2026 20:04:36 +0200 Subject: [PATCH 164/240] Initial version decode Pxr24 compression --- .../Decompressors/B44ExrCompression.cs | 1 + .../Decompressors/Pxr24Compression.cs | 102 ++++++++++++++++++ .../Exr/Compression/ExrDecompressorFactory.cs | 1 + src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index aa4944649..3c4e6b148 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -72,6 +72,7 @@ internal class B44ExrCompression : ExrBaseDecompressor ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); } + // Check if 3-byte encoded flat field. if (this.scratch[2] >= 13 << 2) { Unpack3(this.scratch, this.s); diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs new file mode 100644 index 000000000..9e97fd0ad --- /dev/null +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.IO.Compression; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; + +internal class Pxr24Compression : ExrBaseDecompressor +{ + private readonly IMemoryOwner tmpBuffer; + + private readonly uint rowsPerBlock; + + private readonly int channelCount; + + private readonly int width; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The bytes per pixel row block. + /// The bytes per pixel row. + public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) + : base(allocator, bytesPerBlock, bytesPerRow) + { + this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + this.rowsPerBlock = rowsPerBlock; + this.width = width; + this.channelCount = channelCount; + } + + /// + public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) + { + Span uncompressed = this.tmpBuffer.GetSpan(); + Span outputBuffer = MemoryMarshal.Cast(buffer); + + long pos = stream.Position; + using ZlibInflateStream inflateStream = new( + stream, + () => + { + int left = (int)(compressedBytes - (stream.Position - pos)); + return left > 0 ? left : 0; + }); + inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); + using DeflateStream dataStream = inflateStream.CompressedStream!; + + int totalRead = 0; + while (totalRead < buffer.Length) + { + int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) + { + break; + } + + totalRead += bytesRead; + } + + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); + } + + int lastIn = 0; + int outputOffset = 0; + for (int y = 0; y < this.rowsPerBlock; y++) + { + for (int c = 0; c < this.channelCount; c++) + { + int offsetT1 = lastIn; + lastIn += this.width; + int offsetT2 = lastIn; + lastIn += this.width; + uint pixel = 0; + for (int x = 0; x < this.width; x++) + { + uint t1 = uncompressed[offsetT1]; + uint t2 = uncompressed[offsetT2]; + uint diff = (t1 << 8) | t2; + + pixel += diff; + outputBuffer[outputOffset] = (ushort)pixel; + + offsetT1++; + offsetT2++; + outputOffset++; + } + } + } + } + + /// + protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); +} diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 62519666b..bd8df9230 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -37,6 +37,7 @@ internal static class ExrDecompressorFactory ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), + ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), }; } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 0b75154c2..ddfbcdc18 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -846,7 +846,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// True if the compression is supported; otherwise, false>. private bool IsSupportedCompression() => this.Compression switch { - ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 => true, + ExrCompression.None or ExrCompression.Zip or ExrCompression.Zips or ExrCompression.RunLengthEncoded or ExrCompression.B44 or ExrCompression.Pxr24 => true, _ => false, }; From d805b9b50c96705cacc3d006715cbc7b9d927531 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2026 11:25:28 +0200 Subject: [PATCH 165/240] Add test case for pxr24 compressed image --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 10 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/Calliphora_half_pxr24.exr | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 tests/Images/Input/Exr/Calliphora_half_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index b81dba755..dd958e87a 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -119,6 +119,16 @@ public class ExrDecoderTests image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } + [Theory] + [WithFile(TestImages.Exr.Pxr24Half, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Pxr24Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + [Theory] [WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_B44Compressed(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1a8cf948e..90adae34c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1395,6 +1395,7 @@ public static class TestImages public const string Zips = "Exr/Calliphora_zips.exr"; public const string Rle = "Exr/Calliphora_rle.exr"; public const string B44 = "Exr/Calliphora_b44.exr"; + public const string Pxr24Half = "Exr/Calliphora_half_pxr24.exr"; public const string Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; } diff --git a/tests/Images/Input/Exr/Calliphora_half_pxr24.exr b/tests/Images/Input/Exr/Calliphora_half_pxr24.exr new file mode 100644 index 000000000..17a90699b --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_half_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:927778a73832b5ad68473e46df6be52076d98294271f2cd0ead66691db41b7bf +size 195063 From 344d49d86fb172409430c31b03cc3a3b1798ba2c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 24 Apr 2026 13:26:23 +0200 Subject: [PATCH 166/240] Use Width Property from ExrBaseDecompressor --- .../Compressors/NoneExrCompressor.cs | 5 +-- .../Compressors/ZipExrCompressor.cs | 5 +-- .../Decompressors/B44ExrCompression.cs | 33 +++++++++---------- .../Decompressors/NoneExrCompression.cs | 5 +-- .../Decompressors/Pxr24Compression.cs | 17 ++++++---- .../Decompressors/RunLengthExrCompression.cs | 5 +-- .../Decompressors/ZipExrCompression.cs | 5 +-- .../Exr/Compression/ExrBaseCompression.cs | 4 ++- .../Exr/Compression/ExrBaseDecompressor.cs | 5 +-- .../Exr/Compression/ExrCompressorFactory.cs | 8 +++-- .../Exr/Compression/ExrDecompressorFactory.cs | 8 ++--- .../Formats/Exr/ExrBaseCompressor.cs | 5 +-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 4 +-- 13 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index 58768e990..c1240667e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -17,8 +17,9 @@ internal class NoneExrCompressor : ExrBaseCompressor /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. - public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(output, allocator, bytesPerBlock, bytesPerRow) + /// The witdh of one row in pixels. + public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(output, allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index ef7285da0..bf5f07858 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -24,9 +24,10 @@ internal class ZipExrCompressor : ExrBaseCompressor /// The memory allocator. /// The bytes per block. /// The bytes per row. + /// The witdh of one row in pixels. /// The compression level for deflate compression. - public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, DeflateCompressionLevel compressionLevel) - : base(output, allocator, bytesPerBlock, bytesPerRow) + public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock, bytesPerRow, width) { this.compressionLevel = compressionLevel; this.buffer = allocator.Allocate((int)bytesPerBlock); diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index 3c4e6b148..64774537f 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -13,8 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; /// internal class B44ExrCompression : ExrBaseDecompressor { - private readonly int width; - private readonly uint rowsPerBlock; private readonly int channelCount; @@ -35,9 +33,8 @@ internal class B44ExrCompression : ExrBaseDecompressor /// The width of a pixel row in pixels. /// The number of channels of the image. public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) - : base(allocator, bytesPerBlock, bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow, width) { - this.width = width; this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); @@ -54,17 +51,17 @@ internal class B44ExrCompression : ExrBaseDecompressor { for (int y = 0; y < this.rowsPerBlock; y += 4) { - Span row0 = decompressed.Slice(outputOffset, this.width); - outputOffset += this.width; - Span row1 = decompressed.Slice(outputOffset, this.width); - outputOffset += this.width; - Span row2 = decompressed.Slice(outputOffset, this.width); - outputOffset += this.width; - Span row3 = decompressed.Slice(outputOffset, this.width); - outputOffset += this.width; + Span row0 = decompressed.Slice(outputOffset, this.Width); + outputOffset += this.Width; + Span row1 = decompressed.Slice(outputOffset, this.Width); + outputOffset += this.Width; + Span row2 = decompressed.Slice(outputOffset, this.Width); + outputOffset += this.Width; + Span row3 = decompressed.Slice(outputOffset, this.Width); + outputOffset += this.Width; int rowOffset = 0; - for (int x = 0; x < this.width && bytesLeft > 0; x += 4) + for (int x = 0; x < this.Width && bytesLeft > 0; x += 4) { int bytesRead = stream.Read(this.scratch, 0, 3); if (bytesRead == 0) @@ -90,7 +87,7 @@ internal class B44ExrCompression : ExrBaseDecompressor bytesLeft -= 14; } - int n = x + 3 < this.width ? 4 : this.width - x; + int n = x + 3 < this.Width ? 4 : this.Width - x; if (y + 3 < this.rowsPerBlock) { this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); @@ -125,16 +122,16 @@ internal class B44ExrCompression : ExrBaseDecompressor // Rearrange the decompressed data such that the data for each scan line form a contiguous block. int offsetDecompressed = 0; int offsetOutput = 0; - int blockSize = (int)(this.width * this.rowsPerBlock); + int blockSize = (int)(this.Width * this.rowsPerBlock); for (int y = 0; y < this.rowsPerBlock; y++) { for (int i = 0; i < this.channelCount; i++) { - decompressed.Slice(offsetDecompressed + (i * blockSize), this.width).CopyTo(outputBuffer[offsetOutput..]); - offsetOutput += this.width; + decompressed.Slice(offsetDecompressed + (i * blockSize), this.Width).CopyTo(outputBuffer[offsetOutput..]); + offsetOutput += this.Width; } - offsetDecompressed += this.width; + offsetDecompressed += this.Width; } } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index c15ffbe32..1b045bdeb 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -17,8 +17,9 @@ internal class NoneExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. - public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(allocator, bytesPerBlock, bytesPerRow) + /// The number of pixels per row. + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index 9e97fd0ad..d720c190f 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -10,6 +10,9 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; +/// +/// Implementation of PXR24 decompressor for EXR image data. +/// internal class Pxr24Compression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; @@ -18,20 +21,20 @@ internal class Pxr24Compression : ExrBaseDecompressor private readonly int channelCount; - private readonly int width; - /// /// Initializes a new instance of the class. /// /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. + /// The pixel rows per block. + /// The witdh of one row in pixels. + /// The number of channels for a pixel. public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) - : base(allocator, bytesPerBlock, bytesPerRow) + : base(allocator, bytesPerBlock, bytesPerRow, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); this.rowsPerBlock = rowsPerBlock; - this.width = width; this.channelCount = channelCount; } @@ -76,11 +79,11 @@ internal class Pxr24Compression : ExrBaseDecompressor for (int c = 0; c < this.channelCount; c++) { int offsetT1 = lastIn; - lastIn += this.width; + lastIn += this.Width; int offsetT2 = lastIn; - lastIn += this.width; + lastIn += this.Width; uint pixel = 0; - for (int x = 0; x < this.width; x++) + for (int x = 0; x < this.Width; x++) { uint t1 = uncompressed[offsetT1]; uint t2 = uncompressed[offsetT2]; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 489493d82..29782ae03 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -22,8 +22,9 @@ internal class RunLengthExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per row. - public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + /// The witdh of one row in pixels. + public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index dafe3d832..4333f53cb 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -22,8 +22,9 @@ internal class ZipExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. - public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(allocator, bytesPerBlock, bytesPerRow) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + /// The witdh of one row in pixels. + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index b116dffc6..5912fcdec 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -18,11 +18,13 @@ internal abstract class ExrBaseCompression : IDisposable /// The memory allocator. /// The bytes per block. /// The bytes per row. - protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) + /// The number of pixels of a row. + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) { this.Allocator = allocator; this.BytesPerBlock = bytesPerBlock; this.BytesPerRow = bytesPerRow; + this.Width = width; } /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index efe3c7877..000e6ecea 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -17,8 +17,9 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression /// The memory allocator. /// The bytes per row block. /// The bytes per row. - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(allocator, bytesPerBlock, bytesPerRow) + /// The number of pixels per row. + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index b1b7d2e8e..497bd492a 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -21,6 +21,7 @@ internal static class ExrCompressorFactory /// The output stream. /// The bytes per block. /// The bytes per row. + /// The witdh of one row in pixels. /// The deflate compression level. /// A compressor for EXR image data. public static ExrBaseCompressor Create( @@ -29,11 +30,12 @@ internal static class ExrCompressorFactory Stream output, uint bytesPerBlock, uint bytesPerRow, + int width, DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch { - ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow), - ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), - ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, compressionLevel), + ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), + ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), _ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()), }; } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index bd8df9230..812328458 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -32,10 +32,10 @@ internal static class ExrDecompressorFactory uint rowsPerBlock, int channelCount) => method switch { - ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), - ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), - ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), - ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow), + ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index 1dcdec5be..83887fd5f 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -14,8 +14,9 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. - protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow) - : base(allocator, bytesPerBlock, bytesPerRow) + /// The number of pixels per row. + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + : base(allocator, bytesPerBlock, bytesPerRow, width) => this.Output = output; /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 04ca5e40c..c6824c6aa 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -181,7 +181,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, width); ulong[] rowOffsets = new ulong[height]; for (uint y = 0; y < height; y += rowsPerBlock) @@ -273,7 +273,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, width); Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; From 95ee73e241186b779a4ecfe35e3cd3c06759b282 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 25 Apr 2026 16:10:57 +0200 Subject: [PATCH 167/240] Implement pxr decompression for pixel type uint and float --- .../Decompressors/Pxr24Compression.cs | 106 +++++++++++++++--- .../Exr/Compression/ExrDecompressorFactory.cs | 5 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 20 +++- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index d720c190f..d2fdcfcd1 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.IO.Compression; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -21,6 +22,8 @@ internal class Pxr24Compression : ExrBaseDecompressor private readonly int channelCount; + private readonly ExrPixelType pixelType; + /// /// Initializes a new instance of the class. /// @@ -30,12 +33,14 @@ internal class Pxr24Compression : ExrBaseDecompressor /// The pixel rows per block. /// The witdh of one row in pixels. /// The number of channels for a pixel. - public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) + /// The pixel type. + public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType) : base(allocator, bytesPerBlock, bytesPerRow, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; + this.pixelType = pixelType; } /// @@ -78,23 +83,94 @@ internal class Pxr24Compression : ExrBaseDecompressor { for (int c = 0; c < this.channelCount; c++) { - int offsetT1 = lastIn; - lastIn += this.Width; - int offsetT2 = lastIn; - lastIn += this.Width; - uint pixel = 0; - for (int x = 0; x < this.Width; x++) + switch (this.pixelType) { - uint t1 = uncompressed[offsetT1]; - uint t2 = uncompressed[offsetT2]; - uint diff = (t1 << 8) | t2; + case ExrPixelType.UnsignedInt: + { + int offsetT0 = lastIn; + lastIn += this.Width; + int offsetT1 = lastIn; + lastIn += this.Width; + int offsetT2 = lastIn; + lastIn += this.Width; + int offsetT3 = lastIn; + lastIn += this.Width; + + uint pixel = 0; + for (int x = 0; x < this.Width; x++) + { + uint t0 = uncompressed[offsetT0]; + uint t1 = uncompressed[offsetT1]; + uint t2 = uncompressed[offsetT2]; + uint t3 = uncompressed[offsetT3]; + uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3; + + pixel += diff; + outputBuffer[outputOffset] = (ushort)pixel; + + offsetT0++; + offsetT1++; + offsetT2++; + offsetT3++; + outputOffset++; + } + + break; + } + + case ExrPixelType.Half: + { + int offsetT0 = lastIn; + lastIn += this.Width; + int offsetT1 = lastIn; + lastIn += this.Width; + + uint pixel = 0; + for (int x = 0; x < this.Width; x++) + { + uint t0 = uncompressed[offsetT0]; + uint t1 = uncompressed[offsetT1]; + uint diff = (t0 << 8) | t1; + + pixel += diff; + outputBuffer[outputOffset] = (ushort)pixel; + + offsetT0++; + offsetT1++; + outputOffset++; + } + + break; + } + + case ExrPixelType.Float: + { + int offsetT0 = lastIn; + lastIn += this.Width; + int offsetT1 = lastIn; + lastIn += this.Width; + int offsetT2 = lastIn; + lastIn += this.Width; + + uint pixel = 0; + for (int x = 0; x < this.Width; x++) + { + uint t0 = uncompressed[offsetT0]; + uint t1 = uncompressed[offsetT1]; + uint t2 = uncompressed[offsetT2]; + uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8); + + pixel += diff; + outputBuffer[outputOffset] = (ushort)pixel; - pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + offsetT0++; + offsetT1++; + offsetT2++; + outputOffset++; + } - offsetT1++; - offsetT2++; - outputOffset++; + break; + } } } } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 812328458..6f63a932b 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -30,14 +30,15 @@ internal static class ExrDecompressorFactory uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, - int channelCount) => method switch + int channelCount, + ExrPixelType pixelType) => method switch { ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), - ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), + ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount, pixelType), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), }; } diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index ddfbcdc18..2e60f3a5b 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -154,7 +154,15 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create( + this.Compression, + this.memoryAllocator, + width, + bytesPerBlock, + bytesPerRow, + rowsPerBlock, + channelCount, + this.PixelType); int decodedRows = 0; while (decodedRows < height) @@ -219,7 +227,15 @@ internal sealed class ExrDecoderCore : ImageDecoderCore Span bluePixelData = rowBuffer.GetSpan().Slice(width * 2, width); Span alphaPixelData = rowBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create(this.Compression, this.memoryAllocator, width, bytesPerBlock, bytesPerRow, rowsPerBlock, channelCount); + using ExrBaseDecompressor decompressor = ExrDecompressorFactory.Create( + this.Compression, + this.memoryAllocator, + width, + bytesPerBlock, + bytesPerRow, + rowsPerBlock, + channelCount, + this.PixelType); int decodedRows = 0; while (decodedRows < height) From efa1f8bbae992b0d2c8c239564e0ab394dfadb02 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 09:57:15 +0200 Subject: [PATCH 168/240] Add test case for pxr compression with pixel type float --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Exr/Calliphora_float_pxr24.exr | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 tests/Images/Input/Exr/Calliphora_float_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index dd958e87a..ef2a0100b 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -121,6 +121,7 @@ public class ExrDecoderTests [Theory] [WithFile(TestImages.Exr.Pxr24Half, PixelTypes.Rgba32)] + [WithFile(TestImages.Exr.Pxr24Float, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_Pxr24Compressed(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 90adae34c..b08e26dc7 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1396,6 +1396,7 @@ public static class TestImages public const string Rle = "Exr/Calliphora_rle.exr"; public const string B44 = "Exr/Calliphora_b44.exr"; public const string Pxr24Half = "Exr/Calliphora_half_pxr24.exr"; + public const string Pxr24Float = "Exr/Calliphora_float_pxr24.exr"; public const string Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; } diff --git a/tests/Images/Input/Exr/Calliphora_float_pxr24.exr b/tests/Images/Input/Exr/Calliphora_float_pxr24.exr new file mode 100644 index 000000000..3c5d61e53 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_float_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca6c29eb36395b923298d2bf4ba1f73fd7f081f7b1af2e72b94b92ad2acb638b +size 226997 From e8c9d6db9d2c57dec674933c0a66f6866204b396 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 11:01:36 +0200 Subject: [PATCH 169/240] Add test case for pxr compression with pixel type uint --- .../ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 12 ++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + ...xrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png | 3 +++ tests/Images/Input/Exr/Calliphora_uint_pxr24.exr | 3 +++ 4 files changed, 19 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png create mode 100644 tests/Images/Input/Exr/Calliphora_uint_pxr24.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index ef2a0100b..febdc9dae 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -130,6 +130,18 @@ public class ExrDecoderTests image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } + [Theory] + [WithFile(TestImages.Exr.Pxr24Uint, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + + // Compare to Reference here instead using the reference decoder, since uint pixel type is not supported by the Reference decoder. + image.CompareToReferenceOutput(provider); + } + [Theory] [WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)] public void ExrDecoder_CanDecode_B44Compressed(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b08e26dc7..4f15650cd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1397,6 +1397,7 @@ public static class TestImages public const string B44 = "Exr/Calliphora_b44.exr"; public const string Pxr24Half = "Exr/Calliphora_half_pxr24.exr"; public const string Pxr24Float = "Exr/Calliphora_float_pxr24.exr"; + public const string Pxr24Uint = "Exr/Calliphora_uint_pxr24.exr"; public const string Rgb = "Exr/Calliphora_rgb.exr"; public const string Gray = "Exr/Calliphora_gray.exr"; } diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png new file mode 100644 index 000000000..8f31f0d8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942 +size 163327 diff --git a/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr b/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr new file mode 100644 index 000000000..fd858abb0 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc72685224ba818ac77d914f5e4c91162503f88f775e16b5c745baef6bc7b790 +size 187914 From e917fd317afb08a575380c543da367fc52906e4d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 11:30:08 +0200 Subject: [PATCH 170/240] Fix issues with pxr and pixel type uint and float --- .../Exr/Compression/Decompressors/Pxr24Compression.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index d2fdcfcd1..e15ac4e84 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -47,7 +47,9 @@ internal class Pxr24Compression : ExrBaseDecompressor public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) { Span uncompressed = this.tmpBuffer.GetSpan(); - Span outputBuffer = MemoryMarshal.Cast(buffer); + Span outputBufferHalf = MemoryMarshal.Cast(buffer); + Span outputBufferFloat = MemoryMarshal.Cast(buffer); + Span outputBufferUint = MemoryMarshal.Cast(buffer); long pos = stream.Position; using ZlibInflateStream inflateStream = new( @@ -106,7 +108,7 @@ internal class Pxr24Compression : ExrBaseDecompressor uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3; pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferUint[outputOffset] = pixel; offsetT0++; offsetT1++; @@ -133,7 +135,7 @@ internal class Pxr24Compression : ExrBaseDecompressor uint diff = (t0 << 8) | t1; pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferHalf[outputOffset] = (ushort)pixel; offsetT0++; offsetT1++; @@ -161,7 +163,7 @@ internal class Pxr24Compression : ExrBaseDecompressor uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8); pixel += diff; - outputBuffer[outputOffset] = (ushort)pixel; + outputBufferFloat[outputOffset] = pixel; offsetT0++; offsetT1++; From a85fe63d312b401058b3636ab3c3b50ef3780587 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 14:50:52 +0200 Subject: [PATCH 171/240] Move undoing zip compression into base class --- .../Decompressors/Pxr24Compression.cs | 29 +------------ .../Decompressors/ZipExrCompression.cs | 29 +------------ .../Exr/Compression/ExrBaseDecompressor.cs | 43 +++++++++++++++++++ 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index e15ac4e84..115f455f0 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -51,33 +51,8 @@ internal class Pxr24Compression : ExrBaseDecompressor Span outputBufferFloat = MemoryMarshal.Cast(buffer); Span outputBufferUint = MemoryMarshal.Cast(buffer); - long pos = stream.Position; - using ZlibInflateStream inflateStream = new( - stream, - () => - { - int left = (int)(compressedBytes - (stream.Position - pos)); - return left > 0 ? left : 0; - }); - inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); - using DeflateStream dataStream = inflateStream.CompressedStream!; - - int totalRead = 0; - while (totalRead < buffer.Length) - { - int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; - } - - if (totalRead == 0) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); - } + uint uncompressedBytes = this.BytesPerBlock; + UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes); int lastIn = 0; int outputOffset = 0; diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 4333f53cb..51f8ab4c3 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -31,33 +31,8 @@ internal class ZipExrCompression : ExrBaseDecompressor { Span uncompressed = this.tmpBuffer.GetSpan(); - long pos = stream.Position; - using ZlibInflateStream inflateStream = new( - stream, - () => - { - int left = (int)(compressedBytes - (stream.Position - pos)); - return left > 0 ? left : 0; - }); - inflateStream.AllocateNewBytes((int)this.BytesPerBlock, true); - using DeflateStream dataStream = inflateStream.CompressedStream!; - - int totalRead = 0; - while (totalRead < buffer.Length) - { - int bytesRead = dataStream.Read(uncompressed, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; - } - - if (totalRead == 0) - { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); - } + uint uncompressedBytes = (uint)buffer.Length; + int totalRead = UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes); Reconstruct(uncompressed, (uint)totalRead); Interleave(uncompressed, (uint)totalRead, buffer); diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 000e6ecea..b958982ce 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.IO.Compression; +using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -31,6 +33,47 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression /// The buffer to write the decompressed data to. public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer); + /// + /// Decompresses zip compressed data. + /// + /// The buffered stream to decompress. + /// The compressed bytes. + /// The buffer to write the uncompressed data to. + /// The uncompressed bytes. + /// The total bytes read from the stream. + protected static int UndoZipCompression(BufferedReadStream stream, uint compressedBytes, Span uncompressed, uint uncompressedBytes) + { + long pos = stream.Position; + using ZlibInflateStream inflateStream = new( + stream, + () => + { + int left = (int)(compressedBytes - (stream.Position - pos)); + return left > 0 ? left : 0; + }); + inflateStream.AllocateNewBytes((int)uncompressedBytes, true); + using DeflateStream dataStream = inflateStream.CompressedStream!; + + int totalRead = 0; + while (totalRead < uncompressedBytes) + { + int bytesRead = dataStream.Read(uncompressed, totalRead, (int)uncompressedBytes - totalRead); + if (bytesRead <= 0) + { + break; + } + + totalRead += bytesRead; + } + + if (totalRead == 0) + { + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); + } + + return totalRead; + } + /// /// Integrate over all differences to the previous value in order to /// reconstruct sample values. From 7d38e50d3f3ccb6e544716a91ba5f7f62ee476d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 14:57:27 +0200 Subject: [PATCH 172/240] Use better test image for float pixel type created with reference encoder --- tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs | 3 +-- tests/ImageSharp.Tests/TestImages.cs | 2 +- tests/Images/Input/Exr/Calliphora_float_uncompressed.exr | 3 +++ tests/Images/Input/Exr/rgb_float32_uncompressed.exr | 3 --- 4 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Exr/Calliphora_float_uncompressed.exr delete mode 100644 tests/Images/Input/Exr/rgb_float32_uncompressed.exr diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs index febdc9dae..1ee0cc582 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -36,8 +36,7 @@ public class ExrDecoderTests ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); image.DebugSave(provider); - // There is a 0,0059% difference to the Reference decoder. - image.CompareToOriginal(provider, ImageComparer.Tolerant(0.0005f), ReferenceDecoder); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4f15650cd..2624d1cda 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1388,7 +1388,7 @@ public static class TestImages public const string Benchmark = "Exr/Calliphora_benchmark.exr"; public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; - public const string UncompressedFloatRgb = "Exr/rgb_float32_uncompressed.exr"; + public const string UncompressedFloatRgb = "Exr/Calliphora_float_uncompressed.exr"; public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr"; public const string UintRgba = "Exr/rgba_uint_uncompressed.exr"; public const string Zip = "Exr/Calliphora_zip.exr"; diff --git a/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr new file mode 100644 index 000000000..04a5c035d --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:926996c647c1c54921db0a623e58e5edeb92b447cb4eceb53b8c0e86fd24f11e +size 720281 diff --git a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr b/tests/Images/Input/Exr/rgb_float32_uncompressed.exr deleted file mode 100644 index 489758bb0..000000000 --- a/tests/Images/Input/Exr/rgb_float32_uncompressed.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ab8b71824ca384fc2cde1f74a6f34c38169fa328ccc67f17d08333e9de42f55 -size 243558 From 2bf2e9563853127f1d2f55f2e62ef671741f4924 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 15:12:55 +0200 Subject: [PATCH 173/240] Move rowsPerBlock to base compression class --- .../Compressors/NoneExrCompressor.cs | 5 +++-- .../Compressors/ZipExrCompressor.cs | 5 +++-- .../Decompressors/B44ExrCompression.cs | 19 ++++++++----------- .../Decompressors/NoneExrCompression.cs | 5 +++-- .../Decompressors/Pxr24Compression.cs | 9 ++------- .../Decompressors/RunLengthExrCompression.cs | 4 ++-- .../Decompressors/ZipExrCompression.cs | 7 +++---- .../Exr/Compression/ExrBaseCompression.cs | 9 ++++++++- .../Exr/Compression/ExrBaseDecompressor.cs | 5 +++-- .../Exr/Compression/ExrCompressorFactory.cs | 8 +++++--- .../Exr/Compression/ExrDecompressorFactory.cs | 9 +++++---- .../Formats/Exr/ExrBaseCompressor.cs | 5 +++-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 4 ++-- 13 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs index c1240667e..8aca0da65 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/NoneExrCompressor.cs @@ -17,9 +17,10 @@ internal class NoneExrCompressor : ExrBaseCompressor /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. + /// The pixel rows per block. /// The witdh of one row in pixels. - public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(output, allocator, bytesPerBlock, bytesPerRow, width) + public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs index bf5f07858..01248cc39 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs @@ -24,10 +24,11 @@ internal class ZipExrCompressor : ExrBaseCompressor /// The memory allocator. /// The bytes per block. /// The bytes per row. + /// The pixel rows per block. /// The witdh of one row in pixels. /// The compression level for deflate compression. - public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width, DeflateCompressionLevel compressionLevel) - : base(output, allocator, bytesPerBlock, bytesPerRow, width) + public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, DeflateCompressionLevel compressionLevel) + : base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { this.compressionLevel = compressionLevel; this.buffer = allocator.Allocate((int)bytesPerBlock); diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs index 64774537f..06df7b6a2 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs @@ -13,8 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; /// internal class B44ExrCompression : ExrBaseDecompressor { - private readonly uint rowsPerBlock; - private readonly int channelCount; private readonly byte[] scratch = new byte[14]; @@ -29,13 +27,12 @@ internal class B44ExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per row. - /// The rows per block. + /// The pixel rows per block. /// The width of a pixel row in pixels. /// The number of channels of the image. public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) - : base(allocator, bytesPerBlock, bytesPerRow, width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { - this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.tmpBuffer = allocator.Allocate((int)(width * rowsPerBlock * channelCount)); } @@ -49,7 +46,7 @@ internal class B44ExrCompression : ExrBaseDecompressor int bytesLeft = (int)compressedBytes; for (int i = 0; i < this.channelCount && bytesLeft > 0; i++) { - for (int y = 0; y < this.rowsPerBlock; y += 4) + for (int y = 0; y < this.RowsPerBlock; y += 4) { Span row0 = decompressed.Slice(outputOffset, this.Width); outputOffset += this.Width; @@ -88,7 +85,7 @@ internal class B44ExrCompression : ExrBaseDecompressor } int n = x + 3 < this.Width ? 4 : this.Width - x; - if (y + 3 < this.rowsPerBlock) + if (y + 3 < this.RowsPerBlock) { this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); @@ -98,12 +95,12 @@ internal class B44ExrCompression : ExrBaseDecompressor else { this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); - if (y + 1 < this.rowsPerBlock) + if (y + 1 < this.RowsPerBlock) { this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); } - if (y + 2 < this.rowsPerBlock) + if (y + 2 < this.RowsPerBlock) { this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); } @@ -122,8 +119,8 @@ internal class B44ExrCompression : ExrBaseDecompressor // Rearrange the decompressed data such that the data for each scan line form a contiguous block. int offsetDecompressed = 0; int offsetOutput = 0; - int blockSize = (int)(this.Width * this.rowsPerBlock); - for (int y = 0; y < this.rowsPerBlock; y++) + int blockSize = (int)(this.Width * this.RowsPerBlock); + for (int y = 0; y < this.RowsPerBlock; y++) { for (int i = 0; i < this.channelCount; i++) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs index 1b045bdeb..19edb31af 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/NoneExrCompression.cs @@ -17,9 +17,10 @@ internal class NoneExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. + /// The pixel rows per block. /// The number of pixels per row. - public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs index 115f455f0..f45b660e7 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/Pxr24Compression.cs @@ -2,9 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO.Compression; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -18,8 +16,6 @@ internal class Pxr24Compression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - private readonly uint rowsPerBlock; - private readonly int channelCount; private readonly ExrPixelType pixelType; @@ -35,10 +31,9 @@ internal class Pxr24Compression : ExrBaseDecompressor /// The number of channels for a pixel. /// The pixel type. public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType) - : base(allocator, bytesPerBlock, bytesPerRow, width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); - this.rowsPerBlock = rowsPerBlock; this.channelCount = channelCount; this.pixelType = pixelType; } @@ -56,7 +51,7 @@ internal class Pxr24Compression : ExrBaseDecompressor int lastIn = 0; int outputOffset = 0; - for (int y = 0; y < this.rowsPerBlock; y++) + for (int y = 0; y < this.RowsPerBlock; y++) { for (int c = 0; c < this.channelCount; c++) { diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index 29782ae03..bb3903deb 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -23,8 +23,8 @@ internal class RunLengthExrCompression : ExrBaseDecompressor /// The bytes per pixel row block. /// The bytes per row. /// The witdh of one row in pixels. - public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs index 51f8ab4c3..8bab76f40 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs @@ -2,8 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO.Compression; -using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -22,9 +20,10 @@ internal class ZipExrCompression : ExrBaseDecompressor /// The memory allocator. /// The bytes per pixel row block. /// The bytes per pixel row. + /// The pixel rows per block. /// The witdh of one row in pixels. - public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); + public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); /// public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span buffer) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs index 5912fcdec..72d5be980 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseCompression.cs @@ -18,12 +18,14 @@ internal abstract class ExrBaseCompression : IDisposable /// The memory allocator. /// The bytes per block. /// The bytes per row. + /// The number of pixel rows per block. /// The number of pixels of a row. - protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) + protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) { this.Allocator = allocator; this.BytesPerBlock = bytesPerBlock; this.BytesPerRow = bytesPerRow; + this.RowsPerBlock = rowsPerBlock; this.Width = width; } @@ -47,6 +49,11 @@ internal abstract class ExrBaseCompression : IDisposable /// public uint BytesPerBlock { get; } + /// + /// Gets the number of pixel rows per block. + /// + public uint RowsPerBlock { get; } + /// /// Gets the image width. /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index b958982ce..5dfbdf148 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -19,9 +19,10 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression /// The memory allocator. /// The bytes per row block. /// The bytes per row. + /// The pixel rows per block. /// The number of pixels per row. - protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) { } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs index 497bd492a..0a7d4157f 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrCompressorFactory.cs @@ -21,6 +21,7 @@ internal static class ExrCompressorFactory /// The output stream. /// The bytes per block. /// The bytes per row. + /// The pixel rows per block. /// The witdh of one row in pixels. /// The deflate compression level. /// A compressor for EXR image data. @@ -30,12 +31,13 @@ internal static class ExrCompressorFactory Stream output, uint bytesPerBlock, uint bytesPerRow, + uint rowsPerBlock, int width, DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch { - ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), - ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, width, compressionLevel), + ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), + ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel), + ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel), _ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()), }; } diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs index 6f63a932b..353d1af11 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrDecompressorFactory.cs @@ -22,6 +22,7 @@ internal static class ExrDecompressorFactory /// The bytes per row. /// The rows per block. /// The number of image channels. + /// The pixel type. /// Decompressor for EXR image data. public static ExrBaseDecompressor Create( ExrCompression method, @@ -33,10 +34,10 @@ internal static class ExrDecompressorFactory int channelCount, ExrPixelType pixelType) => method switch { - ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), - ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, width), + ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), + ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), + ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), + ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount, pixelType), _ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), diff --git a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs index 83887fd5f..9e3fe8ec6 100644 --- a/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs +++ b/src/ImageSharp/Formats/Exr/ExrBaseCompressor.cs @@ -14,9 +14,10 @@ internal abstract class ExrBaseCompressor : ExrBaseCompression /// The memory allocator. /// Bytes per row block. /// Bytes per pixel row. + /// The pixel rows per block. /// The number of pixels per row. - protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, int width) - : base(allocator, bytesPerBlock, bytesPerRow, width) + protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) + : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.Output = output; /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index c6824c6aa..e2750d897 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -181,7 +181,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width); ulong[] rowOffsets = new ulong[height]; for (uint y = 0; y < height; y += rowsPerBlock) @@ -273,7 +273,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width); Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; From 7ad873aaf36211abc537ecca6ac7bae1f876d339 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 26 Apr 2026 15:39:18 +0200 Subject: [PATCH 174/240] Fix stylecop warnings --- .../Exr/Compression/Decompressors/RunLengthExrCompression.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs index bb3903deb..f548a8181 100644 --- a/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs +++ b/src/ImageSharp/Formats/Exr/Compression/Decompressors/RunLengthExrCompression.cs @@ -14,14 +14,13 @@ internal class RunLengthExrCompression : ExrBaseDecompressor { private readonly IMemoryOwner tmpBuffer; - private readonly ushort[] s = new ushort[16]; - /// /// Initializes a new instance of the class. /// /// The memory allocator. /// The bytes per pixel row block. /// The bytes per row. + /// The pixel rows per block. /// The witdh of one row in pixels. public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) : base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate((int)bytesPerBlock); From 37f534ea5de4a80b854b3740ffb071ab1bfb47d8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 1 May 2026 10:26:39 +1000 Subject: [PATCH 175/240] Add ImageFrame visitor; use Buffer2D Size/Bounds + expose subregion extensions --- .../Advanced/AdvancedImageExtensions.cs | 9 +++ src/ImageSharp/Advanced/IImageFrameVisitor.cs | 21 +++++++ src/ImageSharp/Advanced/IImageVisitor.cs | 4 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- src/ImageSharp/ImageFrame.cs | 13 ++++ src/ImageSharp/ImageFrame{TPixel}.cs | 15 ++--- src/ImageSharp/Image{TPixel}.cs | 1 - src/ImageSharp/Memory/Buffer2DExtensions.cs | 50 +++++----------- src/ImageSharp/Memory/Buffer2DRegion{T}.cs | 60 +++++++++---------- src/ImageSharp/Memory/Buffer2D{T}.cs | 21 +++++-- .../Transforms/Resize/ResizeWorker.cs | 2 +- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- .../Memory/Buffer2DTests.SwapOrCopyContent.cs | 36 ++++++----- .../Memory/BufferAreaTests.cs | 8 +-- .../Processing/IntegralImageTests.cs | 2 +- .../TestUtilities/TestImageExtensions.cs | 2 +- 16 files changed, 139 insertions(+), 109 deletions(-) create mode 100644 src/ImageSharp/Advanced/IImageFrameVisitor.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index a451e111d..e2c4258e2 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -76,6 +76,15 @@ public static class AdvancedImageExtensions public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) => source.AcceptAsync(visitor, cancellationToken); + /// + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances + /// + /// The source image frame. + /// The image visitor. + public static void AcceptVisitor(this ImageFrame source, IImageFrameVisitor visitor) + => source.Accept(visitor); + /// /// Gets the representation of the pixels as a containing the backing pixel data of the image /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. diff --git a/src/ImageSharp/Advanced/IImageFrameVisitor.cs b/src/ImageSharp/Advanced/IImageFrameVisitor.cs new file mode 100644 index 000000000..f2522110d --- /dev/null +++ b/src/ImageSharp/Advanced/IImageFrameVisitor.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Advanced; + +/// +/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations +/// on non-generic instances. +/// +public interface IImageFrameVisitor +{ + /// + /// Provides a pixel-specific implementation for a given operation. + /// + /// The image frame. + /// The pixel type. + public void Visit(ImageFrame frame) + where TPixel : unmanaged, IPixel; +} diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index 5e8a4e451..aee690967 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -16,7 +16,7 @@ public interface IImageVisitor /// /// The image. /// The pixel type. - void Visit(Image image) + public void Visit(Image image) where TPixel : unmanaged, IPixel; } @@ -33,6 +33,6 @@ public interface IImageVisitorAsync /// The token to monitor for cancellation requests. /// The pixel type. /// A representing the asynchronous operation. - Task VisitAsync(Image image, CancellationToken cancellationToken) + public Task VisitAsync(Image image, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index d2883e281..49640fae0 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -303,7 +303,7 @@ internal sealed class GifEncoderCore this.WriteGraphicalControlExtension(metadata, stream); Buffer2D indices = ((IPixelSource)quantized).PixelBuffer; - Rectangle interest = indices.FullRectangle(); + Rectangle interest = indices.Bounds; bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local); int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index cd6fa6d98..2d726dc4a 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -71,6 +71,19 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable /// Whether to dispose of managed and unmanaged objects. protected abstract void Dispose(bool disposing); + /// + /// Accepts a . + /// Implemented by invoking + /// with the pixel type of the image. + /// + /// The visitor. + internal abstract void Accept(IImageFrameVisitor visitor); + + /// + /// Copies the pixel data of the image frame to a of a specific pixel type. + /// + /// The pixel type of the destination buffer. + /// The buffer to copy the pixel data to. internal abstract void CopyPixelsTo(Buffer2D destination) where TDestinationPixel : unmanaged, IPixel; diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 4e25c4e58..9029d3eec 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -339,6 +339,10 @@ public sealed class ImageFrame : ImageFrame, IPixelSource [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; + /// + internal override void Accept(IImageFrameVisitor visitor) + => visitor.Visit(this); + /// /// Copies the pixels to a of the same size. /// @@ -346,7 +350,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// ImageFrame{TPixel}.CopyTo(): target must be of the same size! internal void CopyTo(Buffer2D target) { - if (this.Size != target.Size()) + if (this.Size != target.Size) { throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } @@ -363,8 +367,8 @@ public sealed class ImageFrame : ImageFrame, IPixelSource { Guard.NotNull(source, nameof(source)); - Buffer2D.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); - this.UpdateSize(this.PixelBuffer.Size()); + _ = Buffer2D.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); + this.UpdateSize(this.PixelBuffer.Size); } /// @@ -475,10 +479,7 @@ public sealed class ImageFrame : ImageFrame, IPixelSource /// Clears the bitmap. /// /// The value to initialize the bitmap with. - internal void Clear(TPixel value) - { - this.PixelBuffer.Clear(value); - } + internal void Clear(TPixel value) => this.PixelBuffer.Clear(value); [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 18db34356..4881518c9 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 96b9bc172..290d978d0 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -104,60 +104,40 @@ public static class Buffer2DExtensions } /// - /// Returns a representing the full area of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle FullRectangle(this Buffer2D buffer) - where T : struct - => new(0, 0, buffer.Width, buffer.Height); - - /// - /// Return a to the subregion represented by 'rectangle' + /// Return a to the subregion represented by . /// /// The element type /// The /// The rectangle subregion /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) + public static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) where T : unmanaged => new(buffer, rectangle); - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) + /// + /// Return a to the specified area of . + /// + /// The element type. + /// The . + /// The X coordinate of the region. + /// The Y coordinate of the region. + /// The region width. + /// The region height. + /// The . + public static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) where T : unmanaged => new(buffer, new Rectangle(x, y, width, height)); /// - /// Return a to the whole area of 'buffer' + /// Return a to the whole area of . /// /// The element type /// The /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer) + public static Buffer2DRegion GetRegion(this Buffer2D buffer) where T : unmanaged => new(buffer); - /// - /// Returns the size of the buffer. - /// - /// The element type - /// The - /// The of the buffer - internal static Size Size(this Buffer2D buffer) - where T : struct => - new(buffer.Width, buffer.Height); - - /// - /// Gets the bounds of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle Bounds(this Buffer2D buffer) - where T : struct => - new(0, 0, buffer.Width, buffer.Height); - [Conditional("DEBUG")] private static void CheckColumnRegionsDoNotOverlap( Buffer2D buffer, diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index 7bfb6f573..c78c4ce9e 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -15,17 +15,17 @@ public readonly struct Buffer2DRegion /// Initializes a new instance of the struct. /// /// The . - /// The defining a rectangular area within the buffer. + /// The defining a rectangular area within the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer, Rectangle rectangle) + public Buffer2DRegion(Buffer2D buffer, Rectangle bounds) { - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); + DebugGuard.MustBeGreaterThanOrEqualTo(bounds.X, 0, nameof(bounds)); + DebugGuard.MustBeGreaterThanOrEqualTo(bounds.Y, 0, nameof(bounds)); + DebugGuard.MustBeLessThanOrEqualTo(bounds.Width, buffer.Width, nameof(bounds)); + DebugGuard.MustBeLessThanOrEqualTo(bounds.Height, buffer.Height, nameof(bounds)); this.Buffer = buffer; - this.Rectangle = rectangle; + this.Bounds = bounds; } /// @@ -34,15 +34,10 @@ public readonly struct Buffer2DRegion /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Buffer2DRegion(Buffer2D buffer) - : this(buffer, buffer.FullRectangle()) + : this(buffer, buffer.Bounds) { } - /// - /// Gets the rectangle specifying the boundaries of the area in . - /// - public Rectangle Rectangle { get; } - /// /// Gets the being pointed by this instance. /// @@ -51,12 +46,12 @@ public readonly struct Buffer2DRegion /// /// Gets the width /// - public int Width => this.Rectangle.Width; + public int Width => this.Bounds.Width; /// /// Gets the height /// - public int Height => this.Rectangle.Height; + public int Height => this.Bounds.Height; /// /// Gets the number of elements between row starts in . @@ -66,12 +61,17 @@ public readonly struct Buffer2DRegion /// /// Gets the size of the area. /// - internal Size Size => this.Rectangle.Size; + public Size Size => this.Bounds.Size; + + /// + /// Gets the rectangle specifying the boundaries of the area in . + /// + public Rectangle Bounds { get; } /// /// Gets a value indicating whether the area refers to the entire /// - internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); + internal bool IsFullBufferArea => this.Size == this.Buffer.Size; /// /// Gets or sets a value at the given index. @@ -79,7 +79,7 @@ public readonly struct Buffer2DRegion /// The position inside a row /// The row index /// The reference to the value - internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; + internal ref T this[int x, int y] => ref this.Buffer[x + this.Bounds.X, y + this.Bounds.Y]; /// /// Gets a span to row 'y' inside this area. @@ -89,9 +89,9 @@ public readonly struct Buffer2DRegion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span DangerousGetRowSpan(int y) { - int yy = this.Rectangle.Y + y; - int xx = this.Rectangle.X; - int width = this.Rectangle.Width; + int yy = this.Bounds.Y + y; + int xx = this.Bounds.X; + int width = this.Bounds.Width; return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); } @@ -114,16 +114,16 @@ public readonly struct Buffer2DRegion /// /// Returns a subregion as . (Similar to .) /// - /// The specifying the boundaries of the subregion + /// The specifying the boundaries of the subregion /// The subregion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Buffer2DRegion GetSubRegion(Rectangle rectangle) { - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Bounds.Width, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Bounds.Height, nameof(rectangle)); - int x = this.Rectangle.X + rectangle.X; - int y = this.Rectangle.Y + rectangle.Y; + int x = this.Bounds.X + rectangle.X; + int y = this.Bounds.Y + rectangle.Y; rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); return new Buffer2DRegion(this.Buffer, rectangle); } @@ -135,8 +135,8 @@ public readonly struct Buffer2DRegion [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref T GetReferenceToOrigin() { - int y = this.Rectangle.Y; - int x = this.Rectangle.X; + int y = this.Bounds.Y; + int x = this.Bounds.X; return ref this.Buffer.DangerousGetRowSpan(y)[x]; } @@ -152,7 +152,7 @@ public readonly struct Buffer2DRegion return; } - for (int y = 0; y < this.Rectangle.Height; y++) + for (int y = 0; y < this.Bounds.Height; y++) { Span row = this.DangerousGetRowSpan(y); row.Clear(); @@ -172,7 +172,7 @@ public readonly struct Buffer2DRegion return; } - for (int y = 0; y < this.Rectangle.Height; y++) + for (int y = 0; y < this.Bounds.Height; y++) { Span row = this.DangerousGetRowSpan(y); row.Fill(value); diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index fe997a4fb..cfd7664f0 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -39,20 +39,30 @@ public sealed class Buffer2D : IDisposable Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride)); this.FastMemoryGroup = memoryGroup; - this.Width = width; - this.Height = height; + this.Size = new Size(width, height); this.RowStride = rowStride; } /// /// Gets the width. /// - public int Width { get; private set; } + public int Width => this.Size.Width; /// /// Gets the height. /// - public int Height { get; private set; } + public int Height => this.Size.Height; + + /// + /// Gets the size of the buffer. + /// + public Size Size { get; private set; } + + /// + /// Gets the bounds of the buffer. + /// + /// The + public Rectangle Bounds => new(0, 0, this.Width, this.Height); /// /// Gets the number of elements between row starts in the backing memory. @@ -402,8 +412,7 @@ public sealed class Buffer2D : IDisposable source.CopyTo(destination); } - (destination.Width, source.Width) = (source.Width, destination.Width); - (destination.Height, source.Height) = (source.Height, destination.Height); + (destination.Size, source.Size) = (source.Size, destination.Size); (destination.RowStride, source.RowStride) = (source.RowStride, destination.RowStride); return swapped; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 0a4d38655..a0a8c2173 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -59,7 +59,7 @@ internal sealed class ResizeWorker : IDisposable { this.configuration = configuration; this.source = source; - this.sourceRectangle = source.Rectangle; + this.sourceRectangle = source.Bounds; this.conversionModifiers = conversionModifiers; this.horizontalKernelMap = horizontalKernelMap; this.verticalKernelMap = verticalKernelMap; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 903a6b076..1610df6eb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -119,7 +119,7 @@ public class SpectralJpegTests this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); averageDifference += average; totalDifference += total; - Size s = libJpegComponent.SpectralBlocks.Size(); + Size s = libJpegComponent.SpectralBlocks.Size; tolerance += s.Width * s.Height; } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs index caf935cc4..6e9069a45 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Memory; @@ -15,26 +15,24 @@ public partial class Buffer2DTests [Fact] public void SwapOrCopyContent_WhenBothAllocated() { - using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) - using (Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) - { - a[1, 3] = 666; - b[1, 3] = 444; + using Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean); + using Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean); + a[1, 3] = 666; + b[1, 3] = 444; - Memory aa = a.FastMemoryGroup.Single(); - Memory bb = b.FastMemoryGroup.Single(); + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); - Buffer2D.SwapOrCopyContent(a, b); + Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.FastMemoryGroup.Single()); - Assert.Equal(aa, b.FastMemoryGroup.Single()); + Assert.Equal(bb, a.FastMemoryGroup.Single()); + Assert.Equal(aa, b.FastMemoryGroup.Single()); - Assert.Equal(new Size(3, 7), a.Size()); - Assert.Equal(new Size(10, 5), b.Size()); + Assert.Equal(new Size(3, 7), a.Size); + Assert.Equal(new Size(10, 5), b.Size); - Assert.Equal(666, b[1, 3]); - Assert.Equal(444, a[1, 3]); - } + Assert.Equal(666, b[1, 3]); + Assert.Equal(444, a[1, 3]); } [Fact] @@ -171,7 +169,7 @@ public partial class Buffer2DTests bool swap = Buffer2D.SwapOrCopyContent(dest, source); Assert.False(swap); - Assert.Equal(new Size(3, 2), dest.Size()); + Assert.Equal(new Size(3, 2), dest.Size); Assert.Equal(6, dest[2, 1]); } @@ -191,7 +189,7 @@ public partial class Buffer2DTests bool swap = Buffer2D.SwapOrCopyContent(dest, source); Assert.False(swap); - Assert.Equal(new Size(1, 5), dest.Size()); + Assert.Equal(new Size(1, 5), dest.Size); Assert.Equal(1, dest[0, 0]); Assert.Equal(2, dest[0, 1]); Assert.Equal(3, dest[0, 2]); @@ -215,7 +213,7 @@ public partial class Buffer2DTests bool swap = Buffer2D.SwapOrCopyContent(dest, source); Assert.False(swap); - Assert.Equal(new Size(2, 2), dest.Size()); + Assert.Equal(new Size(2, 2), dest.Size); Assert.Equal(1, dest[0, 0]); Assert.Equal(2, dest[1, 0]); Assert.Equal(3, dest[0, 1]); diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index be7edbacc..a44d6ce1a 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -17,7 +17,7 @@ public class BufferAreaTests Buffer2DRegion area = new(buffer, rectangle); Assert.Equal(buffer, area.Buffer); - Assert.Equal(rectangle, area.Rectangle); + Assert.Equal(rectangle, area.Bounds); } private Buffer2D CreateTestBuffer(int w, int h) @@ -90,7 +90,7 @@ public class BufferAreaTests Rectangle expectedRect = new(10, 12, 5, 5); Assert.Equal(buffer, area1.Buffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(expectedRect, area1.Bounds); int value00 = (12 * 100) + 10; Assert.Equal(value00, area1[0, 0]); @@ -147,9 +147,9 @@ public class BufferAreaTests Assert.Equal(0, buffer[5, 5]); Assert.Equal(0, buffer[14, 14]); - for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) + for (int y = region.Bounds.Y; y < region.Bounds.Bottom; y++) { - Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Span span = buffer.DangerousGetRowSpan(y).Slice(region.Bounds.X, region.Width); Assert.True(span.SequenceEqual(new int[region.Width])); } } diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs index 81ba1693d..a0ea99a9d 100644 --- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs +++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs @@ -76,7 +76,7 @@ public class IntegralImageTests : BaseImageOperationsExtensionTest Buffer2D integralBuffer, Func getPixel) where TPixel : unmanaged, IPixel - => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); + => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds, getPixel); private static void VerifySumValues( TestImageProvider provider, diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 4a34529dd..be2356d65 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -505,7 +505,7 @@ public static class TestImageExtensions public static void CompareBuffers(Buffer2D expected, Buffer2D actual) where T : struct, IEquatable { - Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + Assert.True(expected.Size == actual.Size, "Buffer sizes are not equal!"); for (int y = 0; y < expected.Height; y++) { From c1de5b61d3bcd96074dfa857b0d1f81f7ee10e95 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 2 May 2026 16:18:12 +0200 Subject: [PATCH 176/240] Fix mistake in inflateStream.AllocateNewBytes: Use compressedBytes instead of uncompressedBytes --- src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 5dfbdf148..1b2d95ba5 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -52,7 +52,7 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression int left = (int)(compressedBytes - (stream.Position - pos)); return left > 0 ? left : 0; }); - inflateStream.AllocateNewBytes((int)uncompressedBytes, true); + inflateStream.AllocateNewBytes((int)compressedBytes, true); using DeflateStream dataStream = inflateStream.CompressedStream!; int totalRead = 0; @@ -69,7 +69,7 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression if (totalRead == 0) { - ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed image data!"); + ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed EXR image data!"); } return totalRead; From 1e65e08fc1f2d2051e63918da0b9343476367c9a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 11 May 2026 22:22:52 +1000 Subject: [PATCH 177/240] Add additional unit tests to improve code coverage. Update licensing version --- .gitattributes | 16 +- ImageSharp.sln | 5 +- shared-infrastructure | 2 +- .../WorkingSpaces/GammaWorkingSpace.cs | 1 + .../WorkingSpaces/RgbWorkingSpace.cs | 6 +- src/ImageSharp/Common/Tuples/Octet{T}.cs | 72 ---- tests/Directory.Build.targets | 5 - .../PixelConversion_Rgba32_To_Bgra32.cs | 11 +- .../Vectorization/WidenBytesToUInt32.cs | 18 +- .../ColorProfiles/RgbWorkingSpaceTests.cs | 122 ++++++ .../Common/SimdUtilsTests.Shuffle.cs | 4 +- .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 4 +- .../Formats/Jpg/Block8x8FTests.cs | 4 +- .../ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 4 +- .../Formats/Jpg/JpegColorConverterTests.cs | 8 +- .../PixelFormats/PixelBlenderTests.cs | 350 ++++++++++++++++++ .../PorterDuffCompositorTests.cs | 2 +- .../FeatureTesting/FeatureTestRunner.cs | 2 +- tests/coverlet.runsettings | 2 +- 19 files changed, 524 insertions(+), 114 deletions(-) delete mode 100644 src/ImageSharp/Common/Tuples/Octet{T}.cs create mode 100644 tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs diff --git a/.gitattributes b/.gitattributes index f7bd4d061..7de2c1597 100644 --- a/.gitattributes +++ b/.gitattributes @@ -85,18 +85,12 @@ ############################################################################### *.basis binary *.dll binary -*.eot binary *.exe binary -*.otf binary *.pdf binary *.ppt binary *.pptx binary *.pvr binary *.snk binary -*.ttc binary -*.ttf binary -*.woff binary -*.woff2 binary *.xls binary *.xlsx binary ############################################################################### @@ -126,6 +120,7 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.astc filter=lfs diff=lfs merge=lfs -text *.pam filter=lfs diff=lfs merge=lfs -text *.pbm filter=lfs diff=lfs merge=lfs -text *.pgm filter=lfs diff=lfs merge=lfs -text @@ -143,3 +138,12 @@ # Handle ICC files by git lfs ############################################################################### *.icc filter=lfs diff=lfs merge=lfs -text +############################################################################### +# Handle font files by git lfs +############################################################################### +*.eot filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.ttc filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text diff --git a/ImageSharp.sln b/ImageSharp.sln index 13dd2fba7..7a5204037 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11723.231 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -45,6 +45,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageShar EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" ProjectSection(SolutionItems) = preProject + tests\coverlet.runsettings = tests\coverlet.runsettings tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets tests\ImageSharp.Tests.ruleset = tests\ImageSharp.Tests.ruleset diff --git a/shared-infrastructure b/shared-infrastructure index a1d3ac204..7ac570345 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d +Subproject commit 7ac5703452348d9295db31fc0912c2bd9e419dc9 diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs index 91fa42624..a3959f12e 100644 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs @@ -62,6 +62,7 @@ public sealed class GammaWorkingSpace : RgbWorkingSpace /// public override int GetHashCode() => HashCode.Combine( + typeof(GammaWorkingSpace), this.WhitePoint, this.ChromaticityCoordinates, this.Gamma); diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs index 97069b856..b278dd317 100644 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs +++ b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs @@ -70,8 +70,10 @@ public abstract class RgbWorkingSpace return true; } - if (obj is RgbWorkingSpace other) + if (obj.GetType() == this.GetType()) { + RgbWorkingSpace other = (RgbWorkingSpace)obj; + return this.WhitePoint.Equals(other.WhitePoint) && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); } @@ -81,5 +83,5 @@ public abstract class RgbWorkingSpace /// public override int GetHashCode() - => HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); + => HashCode.Combine(this.GetType(), this.WhitePoint, this.ChromaticityCoordinates); } diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs deleted file mode 100644 index f73bdd339..000000000 --- a/src/ImageSharp/Common/Tuples/Octet{T}.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples; - -/// -/// Contains 8 element value tuples of various types. -/// -[StructLayout(LayoutKind.Sequential)] -internal struct Octet - where T : unmanaged -{ - public T V0; - public T V1; - public T V2; - public T V3; - public T V4; - public T V5; - public T V6; - public T V7; - - /// - public override readonly string ToString() - { - return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } -} - -/// -/// Extension methods for the type. -/// -internal static class OctetExtensions -{ - /// - /// Loads the fields in a target of from one of type. - /// - /// The target of instance. - /// The source of instance. - [MethodImpl(InliningOptions.ShortMethod)] - public static void LoadFrom(ref this Octet destination, ref Octet source) - { - destination.V0 = source.V0; - destination.V1 = source.V1; - destination.V2 = source.V2; - destination.V3 = source.V3; - destination.V4 = source.V4; - destination.V5 = source.V5; - destination.V6 = source.V6; - destination.V7 = source.V7; - } - - /// - /// Loads the fields in a target of from one of type. - /// - /// The target of instance. - /// The source of instance. - [MethodImpl(InliningOptions.ShortMethod)] - public static void LoadFrom(ref this Octet destination, ref Octet source) - { - destination.V0 = (byte)source.V0; - destination.V1 = (byte)source.V1; - destination.V2 = (byte)source.V2; - destination.V3 = (byte)source.V3; - destination.V4 = (byte)source.V4; - destination.V5 = (byte)source.V5; - destination.V6 = (byte)source.V6; - destination.V7 = (byte)source.V7; - } -} diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 6b25509ed..71f2bc333 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -19,11 +19,6 @@ - - diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs index 232d8b3e2..c69510bc4 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; @@ -225,8 +224,8 @@ public class PixelConversion_Rgba32_To_Bgra32 // [Benchmark] public void Bitops_Simd() { - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]); + ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]); for (nuint i = 0; i < (uint)this.Count / 8; i++) { @@ -249,9 +248,9 @@ public class PixelConversion_Rgba32_To_Bgra32 #pragma warning restore SA1132 // Do not combine fields [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet s, ref Octet d) + private static void BitopsSimdImpl(ref InlineArray8 s, ref InlineArray8 d) { - Vector sVec = Unsafe.As, Vector>(ref s); + Vector sVec = Unsafe.As, Vector>(ref s); Vector aMask = new(0xFF00FF00); Vector bMask = new(0x00FF00FF); @@ -274,7 +273,7 @@ public class PixelConversion_Rgba32_To_Bgra32 Vector cc = Unsafe.As>(ref c); Vector dd = aa + cc; - d = Unsafe.As, Octet>(ref dd); + d = Unsafe.As, InlineArray8>(ref dd); } // [Benchmark] diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index da7ddae41..fd17262de 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -5,8 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Tuples; - namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; [Config(typeof(Config.Short))] @@ -30,12 +28,22 @@ public class WidenBytesToUInt32 { const int N = Count / 8; - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]); + ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]); for (nuint i = 0; i < N; i++) { - Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); + ref InlineArray8 source = ref Unsafe.Add(ref sBase, i); + ref InlineArray8 destination = ref Unsafe.Add(ref dBase, i); + + destination[0] = source[0]; + destination[1] = source[1]; + destination[2] = source[2]; + destination[3] = source[3]; + destination[4] = source[4]; + destination[5] = source[5]; + destination[6] = source[6]; + destination[7] = source[7]; } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs new file mode 100644 index 000000000..8a38ea11f --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests the class. +/// +[Trait("Color", "Conversion")] +public class RgbWorkingSpaceTests +{ + private static readonly ApproximateFloatComparer TolerantComparer = new(1e-5F); + + public static readonly TheoryData WorkingSpaces = new() + { + KnownRgbWorkingSpaces.SRgb, + KnownRgbWorkingSpaces.SRgbSimplified, + KnownRgbWorkingSpaces.Rec709, + KnownRgbWorkingSpaces.Rec2020, + KnownRgbWorkingSpaces.ECIRgbv2, + KnownRgbWorkingSpaces.AdobeRgb1998, + KnownRgbWorkingSpaces.ApplesRgb, + KnownRgbWorkingSpaces.BestRgb, + KnownRgbWorkingSpaces.BetaRgb, + KnownRgbWorkingSpaces.BruceRgb, + KnownRgbWorkingSpaces.CIERgb, + KnownRgbWorkingSpaces.ColorMatchRgb, + KnownRgbWorkingSpaces.DonRgb4, + KnownRgbWorkingSpaces.EktaSpacePS5, + KnownRgbWorkingSpaces.NTSCRgb, + KnownRgbWorkingSpaces.PALSECAMRgb, + KnownRgbWorkingSpaces.ProPhotoRgb, + KnownRgbWorkingSpaces.SMPTECRgb, + KnownRgbWorkingSpaces.WideGamutRgb + }; + + [Fact] + public void RgbWorkingSpaceEqualityRequiresSameConcreteType() + { + RgbWorkingSpace sRgb = KnownRgbWorkingSpaces.SRgb; + RgbWorkingSpace sRgbSimplified = KnownRgbWorkingSpaces.SRgbSimplified; + RgbWorkingSpace rec709 = KnownRgbWorkingSpaces.Rec709; + + Assert.False(sRgb.Equals(sRgbSimplified)); + Assert.False(sRgbSimplified.Equals(sRgb)); + Assert.False(sRgb.Equals(rec709)); + Assert.False(rec709.Equals(sRgb)); + + Assert.NotEqual(sRgb.GetHashCode(), sRgbSimplified.GetHashCode()); + Assert.NotEqual(sRgb.GetHashCode(), rec709.GetHashCode()); + } + + [Fact] + public void RgbWorkingSpaceEqualityMatchesSameConcreteTypeValues() + { + RgbWorkingSpace x = new SRgbWorkingSpace( + KnownRgbWorkingSpaces.SRgb.WhitePoint, + KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates); + + RgbWorkingSpace y = new SRgbWorkingSpace( + KnownRgbWorkingSpaces.SRgb.WhitePoint, + KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates); + + Assert.Equal(x, y); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Fact] + public void GammaWorkingSpaceEqualityIncludesGamma() + { + GammaWorkingSpace x = new( + 2.2F, + KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint, + KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates); + + GammaWorkingSpace y = new( + 1.8F, + KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint, + KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates); + + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [MemberData(nameof(WorkingSpaces))] + public void CompressAndExpand_RoundTripsWithTolerance(RgbWorkingSpace workingSpace) + { + Vector4[] linear = + [ + new(0F, .001F, .18F, 1F), // Endpoint, below the sRGB breakpoint, common middle gray, and opaque alpha. + new(.0031308F, .25F, .5F, .75F), // sRGB linear/gamma breakpoint with interior values and partial alpha. + new(.75F, .5F, .25F, .5F), // Reversed interior values ensure channel order does not hide errors. + new(1F, .8F, .2F, .25F) // Upper endpoint, high/low interior values, and low alpha. + ]; + + Vector4[] expectedCompressed = new Vector4[linear.Length]; + + for (int i = 0; i < linear.Length; i++) + { + Vector4 compressed = workingSpace.Compress(linear[i]); + Vector4 expanded = workingSpace.Expand(compressed); + + expectedCompressed[i] = compressed; + + Assert.Equal(linear[i], expanded, TolerantComparer); + } + + Vector4[] actualCompressed = linear.ToArray(); + workingSpace.Compress(actualCompressed); + + Assert.Equal(expectedCompressed, actualCompressed, TolerantComparer); + + workingSpace.Expand(actualCompressed); + + Assert.Equal(linear, actualCompressed, TolerantComparer); + } +} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index 81daac3e0..12b4c3426 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -292,7 +292,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -478,7 +478,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } private static void TestShuffleFloat4Channel( diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index e39f9456f..dfd0a6b30 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -133,7 +133,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2); } [Theory] @@ -171,7 +171,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 544ac85a5..610693c7f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -219,7 +219,7 @@ public partial class Block8x8FTests : JpegFixture RunTest, srcSeed, qtSeed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Fact] @@ -391,7 +391,7 @@ public partial class Block8x8FTests : JpegFixture // 3. DisableAvx2 - call fallback code of float implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 83c3a344d..b17a39f38 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -152,7 +152,7 @@ public static class DCTTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -359,7 +359,7 @@ public static class DCTTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 8d94fb5cf..ba0dce4c5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -69,7 +69,7 @@ public class JpegColorConverterTests { FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); static void RunTest(string arg) { @@ -102,7 +102,7 @@ public class JpegColorConverterTests { FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); static void RunTest(string arg) { @@ -168,7 +168,7 @@ public class JpegColorConverterTests { FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); static void RunTest(string arg) { @@ -201,7 +201,7 @@ public class JpegColorConverterTests { FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); static void RunTest(string arg) { diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 924e94d92..ddd79bb75 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -32,6 +33,172 @@ public class PixelBlenderTests { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, }; + public static TheoryData BlenderModeMappings + { + get + { + TheoryData data = new(); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Clear, + typeof(DefaultPixelBlenders.NormalClear), + typeof(DefaultPixelBlenders.MultiplyClear), + typeof(DefaultPixelBlenders.AddClear), + typeof(DefaultPixelBlenders.SubtractClear), + typeof(DefaultPixelBlenders.ScreenClear), + typeof(DefaultPixelBlenders.DarkenClear), + typeof(DefaultPixelBlenders.LightenClear), + typeof(DefaultPixelBlenders.OverlayClear), + typeof(DefaultPixelBlenders.HardLightClear)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Xor, + typeof(DefaultPixelBlenders.NormalXor), + typeof(DefaultPixelBlenders.MultiplyXor), + typeof(DefaultPixelBlenders.AddXor), + typeof(DefaultPixelBlenders.SubtractXor), + typeof(DefaultPixelBlenders.ScreenXor), + typeof(DefaultPixelBlenders.DarkenXor), + typeof(DefaultPixelBlenders.LightenXor), + typeof(DefaultPixelBlenders.OverlayXor), + typeof(DefaultPixelBlenders.HardLightXor)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Src, + typeof(DefaultPixelBlenders.NormalSrc), + typeof(DefaultPixelBlenders.MultiplySrc), + typeof(DefaultPixelBlenders.AddSrc), + typeof(DefaultPixelBlenders.SubtractSrc), + typeof(DefaultPixelBlenders.ScreenSrc), + typeof(DefaultPixelBlenders.DarkenSrc), + typeof(DefaultPixelBlenders.LightenSrc), + typeof(DefaultPixelBlenders.OverlaySrc), + typeof(DefaultPixelBlenders.HardLightSrc)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcAtop, + typeof(DefaultPixelBlenders.NormalSrcAtop), + typeof(DefaultPixelBlenders.MultiplySrcAtop), + typeof(DefaultPixelBlenders.AddSrcAtop), + typeof(DefaultPixelBlenders.SubtractSrcAtop), + typeof(DefaultPixelBlenders.ScreenSrcAtop), + typeof(DefaultPixelBlenders.DarkenSrcAtop), + typeof(DefaultPixelBlenders.LightenSrcAtop), + typeof(DefaultPixelBlenders.OverlaySrcAtop), + typeof(DefaultPixelBlenders.HardLightSrcAtop)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcIn, + typeof(DefaultPixelBlenders.NormalSrcIn), + typeof(DefaultPixelBlenders.MultiplySrcIn), + typeof(DefaultPixelBlenders.AddSrcIn), + typeof(DefaultPixelBlenders.SubtractSrcIn), + typeof(DefaultPixelBlenders.ScreenSrcIn), + typeof(DefaultPixelBlenders.DarkenSrcIn), + typeof(DefaultPixelBlenders.LightenSrcIn), + typeof(DefaultPixelBlenders.OverlaySrcIn), + typeof(DefaultPixelBlenders.HardLightSrcIn)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcOut, + typeof(DefaultPixelBlenders.NormalSrcOut), + typeof(DefaultPixelBlenders.MultiplySrcOut), + typeof(DefaultPixelBlenders.AddSrcOut), + typeof(DefaultPixelBlenders.SubtractSrcOut), + typeof(DefaultPixelBlenders.ScreenSrcOut), + typeof(DefaultPixelBlenders.DarkenSrcOut), + typeof(DefaultPixelBlenders.LightenSrcOut), + typeof(DefaultPixelBlenders.OverlaySrcOut), + typeof(DefaultPixelBlenders.HardLightSrcOut)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Dest, + typeof(DefaultPixelBlenders.NormalDest), + typeof(DefaultPixelBlenders.MultiplyDest), + typeof(DefaultPixelBlenders.AddDest), + typeof(DefaultPixelBlenders.SubtractDest), + typeof(DefaultPixelBlenders.ScreenDest), + typeof(DefaultPixelBlenders.DarkenDest), + typeof(DefaultPixelBlenders.LightenDest), + typeof(DefaultPixelBlenders.OverlayDest), + typeof(DefaultPixelBlenders.HardLightDest)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestAtop, + typeof(DefaultPixelBlenders.NormalDestAtop), + typeof(DefaultPixelBlenders.MultiplyDestAtop), + typeof(DefaultPixelBlenders.AddDestAtop), + typeof(DefaultPixelBlenders.SubtractDestAtop), + typeof(DefaultPixelBlenders.ScreenDestAtop), + typeof(DefaultPixelBlenders.DarkenDestAtop), + typeof(DefaultPixelBlenders.LightenDestAtop), + typeof(DefaultPixelBlenders.OverlayDestAtop), + typeof(DefaultPixelBlenders.HardLightDestAtop)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestIn, + typeof(DefaultPixelBlenders.NormalDestIn), + typeof(DefaultPixelBlenders.MultiplyDestIn), + typeof(DefaultPixelBlenders.AddDestIn), + typeof(DefaultPixelBlenders.SubtractDestIn), + typeof(DefaultPixelBlenders.ScreenDestIn), + typeof(DefaultPixelBlenders.DarkenDestIn), + typeof(DefaultPixelBlenders.LightenDestIn), + typeof(DefaultPixelBlenders.OverlayDestIn), + typeof(DefaultPixelBlenders.HardLightDestIn)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestOut, + typeof(DefaultPixelBlenders.NormalDestOut), + typeof(DefaultPixelBlenders.MultiplyDestOut), + typeof(DefaultPixelBlenders.AddDestOut), + typeof(DefaultPixelBlenders.SubtractDestOut), + typeof(DefaultPixelBlenders.ScreenDestOut), + typeof(DefaultPixelBlenders.DarkenDestOut), + typeof(DefaultPixelBlenders.LightenDestOut), + typeof(DefaultPixelBlenders.OverlayDestOut), + typeof(DefaultPixelBlenders.HardLightDestOut)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestOver, + typeof(DefaultPixelBlenders.NormalDestOver), + typeof(DefaultPixelBlenders.MultiplyDestOver), + typeof(DefaultPixelBlenders.AddDestOver), + typeof(DefaultPixelBlenders.SubtractDestOver), + typeof(DefaultPixelBlenders.ScreenDestOver), + typeof(DefaultPixelBlenders.DarkenDestOver), + typeof(DefaultPixelBlenders.LightenDestOver), + typeof(DefaultPixelBlenders.OverlayDestOver), + typeof(DefaultPixelBlenders.HardLightDestOver)); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcOver, + typeof(DefaultPixelBlenders.NormalSrcOver), + typeof(DefaultPixelBlenders.MultiplySrcOver), + typeof(DefaultPixelBlenders.AddSrcOver), + typeof(DefaultPixelBlenders.SubtractSrcOver), + typeof(DefaultPixelBlenders.ScreenSrcOver), + typeof(DefaultPixelBlenders.DarkenSrcOver), + typeof(DefaultPixelBlenders.LightenSrcOver), + typeof(DefaultPixelBlenders.OverlaySrcOver), + typeof(DefaultPixelBlenders.HardLightSrcOver)); + + return data; + } + } + [Theory] [MemberData(nameof(BlenderMappings))] public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) @@ -41,6 +208,126 @@ public class PixelBlenderTests Assert.IsType(type, blender); } + [Theory] + [MemberData(nameof(BlenderModeMappings))] + public void ReturnsCorrectBlenderForAllModeCombinations(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode, Type type) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + Assert.IsType(type, blender); + } + + [Fact] + public void BlendFunctionsAreCalledForAllModeCombinations() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + ExerciseAllBlenderModeCombinations, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void Blend_WithConstantSourceAndSingleAmount() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + + blender.Blend(Configuration.Default, destination, background, source, 1F); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceSingleAmountAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.Blend(Configuration.Default, destination, background, source, 1F, workingBuffer); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceAndAmountSpan() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + float[] amount = [1F, 1F]; + + blender.Blend(Configuration.Default, destination, background, source, amount); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceAmountSpanAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + float[] amount = [1F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithSourceSpanAmountSpanAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32[] source = + [ + Color.Blue.ToPixel(), + Color.Yellow.ToPixel() + ]; + + float[] amount = [1F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 3]; + + blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer); + + Assert.Equal(source[0], destination[0]); + Assert.Equal(source[1], destination[1]); + } + public static TheoryData ColorBlendingExpectedResults = new() { { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel() }, @@ -92,4 +379,67 @@ public class PixelBlenderTests // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); } + + private static void AddBlenderModeMappings( + TheoryData data, + PixelAlphaCompositionMode alphaMode, + Type normal, + Type multiply, + Type add, + Type subtract, + Type screen, + Type darken, + Type lighten, + Type overlay, + Type hardLight) + { + data.Add(PixelColorBlendingMode.Normal, alphaMode, normal); + data.Add(PixelColorBlendingMode.Multiply, alphaMode, multiply); + data.Add(PixelColorBlendingMode.Add, alphaMode, add); + data.Add(PixelColorBlendingMode.Subtract, alphaMode, subtract); + data.Add(PixelColorBlendingMode.Screen, alphaMode, screen); + data.Add(PixelColorBlendingMode.Darken, alphaMode, darken); + data.Add(PixelColorBlendingMode.Lighten, alphaMode, lighten); + data.Add(PixelColorBlendingMode.Overlay, alphaMode, overlay); + data.Add(PixelColorBlendingMode.HardLight, alphaMode, hardLight); + } + + private static void ExerciseAllBlenderModeCombinations() + { + foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues()) + { + foreach (PixelColorBlendingMode colorMode in Enum.GetValues()) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + ExerciseBlender(blender); + } + } + } + + private static void ExerciseBlender(PixelBlender blender) + { + Rgba32 background = Color.MistyRose.ToPixel(); + Rgba32 source = Color.MidnightBlue.ToPixel(); + float[] amount = [1F, 1F, 1F, 1F]; + + Rgba32 expected = blender.Blend(background, source, 1F); + + Rgba32[] destination = new Rgba32[4]; + Rgba32[] backgroundSpan = [background, background, background, background]; + Rgba32[] 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); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.Blend(Configuration.Default, destination, backgroundSpan, source, 1F, constantSourceBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 994b7d02e..f8c296846 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -59,7 +59,7 @@ public class PorterDuffCompositorTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX, provider, mode.ToString()); } diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index be3e9ccd5..4cd30fc57 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -439,7 +439,7 @@ public enum HwIntrinsics : long DisableSSE42 = 1L << 1, DisableAVX = 1L << 2, DisableAVX2 = 1L << 3, - DisableAVX512 = 1L << 4, + DisableAVX512F = 1L << 4, DisableAVX512v2 = 1L << 5, DisableAVX512v3 = 1L << 6, DisableAVX10v1 = 1L << 7, diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings index cffce3540..f327b47a2 100644 --- a/tests/coverlet.runsettings +++ b/tests/coverlet.runsettings @@ -9,7 +9,7 @@ lcov - [SixLabors.*]* + [SixLabors.ImageSharp*]* true From c795d8140444b410659b58eed9f960cf1660a08f Mon Sep 17 00:00:00 2001 From: Sven Claesson Date: Tue, 12 May 2026 09:30:50 +0200 Subject: [PATCH 178/240] Fix integer overflow and bounds-checking vulnerabilities in EXR decoder (#3126) * Fix integer overflow and bounds-checking vulnerabilities in EXR decoder Use ulong arithmetic in CalculateBytesPerRow and block size calculations to prevent integer overflow. Add validation for DataWindow dimensions, block size limits, and row offsets outside stream bounds. * Harden EXR row offset validation * EXR: validate sizes, prevent overflows, dispose image --------- Co-authored-by: James Jackson-South --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 117 ++++++-- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 20 +- src/ImageSharp/Formats/Exr/ExrUtils.cs | 8 +- .../Formats/Exr/ExrDecoderSecurityTests.cs | 274 ++++++++++++++++++ 4 files changed, 384 insertions(+), 35 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 2e60f3a5b..c24352d57 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -91,6 +91,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore /// private ExrHeaderAttributes HeaderAttributes { get; set; } + /// + /// Gets or sets the earliest valid stream position for a scanline chunk. + /// + private long MinimumChunkOffset { get; set; } + /// protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) { @@ -100,24 +105,33 @@ internal sealed class ExrDecoderCore : ImageDecoderCore ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported"); } - Image image = new(this.configuration, this.Width, this.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + Image image = null; + try + { + image = new Image(this.configuration, this.Width, this.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + switch (this.PixelType) + { + case ExrPixelType.Half: + case ExrPixelType.Float: + this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken); + break; + case ExrPixelType.UnsignedInt: + this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken); + break; + default: + ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); + break; + } - switch (this.PixelType) + return image; + } + catch { - case ExrPixelType.Half: - case ExrPixelType.Float: - this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken); - break; - case ExrPixelType.UnsignedInt: - this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken); - break; - default: - ExrThrowHelper.ThrowNotSupported("Pixel type is not supported"); - break; + image?.Dispose(); + throw; } - - return image; } /// @@ -139,9 +153,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); + ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; + ulong bytesPerBlock = bytesPerRow * rowsPerBlock; + if (bytesPerBlock > int.MaxValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR block size exceeds the maximum allowed size."); + } + int width = this.Width; int height = this.Height; int channelCount = this.Channels.Count; @@ -158,8 +177,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Compression, this.memoryAllocator, width, - bytesPerBlock, - bytesPerRow, + (uint)bytesPerBlock, + (uint)bytesPerRow, rowsPerBlock, channelCount, this.PixelType); @@ -170,6 +189,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; + this.ValidateChunkOffset(rowOffset, stream); stream.Position = (long)rowOffset; uint rowStartIndex = this.ReadUnsignedInteger(stream); @@ -212,9 +232,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { bool hasAlpha = this.HasAlpha(); - uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); + ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width); uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; + ulong bytesPerBlock = bytesPerRow * rowsPerBlock; + if (bytesPerBlock > int.MaxValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR block size exceeds the maximum allowed size."); + } + int width = this.Width; int height = this.Height; int channelCount = this.Channels.Count; @@ -231,8 +256,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.Compression, this.memoryAllocator, width, - bytesPerBlock, - bytesPerRow, + (uint)bytesPerBlock, + (uint)bytesPerRow, rowsPerBlock, channelCount, this.PixelType); @@ -243,6 +268,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore ulong rowOffset = this.ReadUnsignedLong(stream); long nextRowOffsetPosition = stream.Position; + this.ValidateChunkOffset(rowOffset, stream); stream.Position = (long)rowOffset; uint rowStartIndex = this.ReadUnsignedInteger(stream); @@ -597,10 +623,38 @@ internal sealed class ExrDecoderCore : ImageDecoderCore this.HeaderAttributes = this.ParseHeaderAttributes(stream); - this.Width = this.HeaderAttributes.DataWindow.XMax - this.HeaderAttributes.DataWindow.XMin + 1; - this.Height = this.HeaderAttributes.DataWindow.YMax - this.HeaderAttributes.DataWindow.YMin + 1; + ExrBox2i dataWindow = this.HeaderAttributes.DataWindow; + if (dataWindow.XMax < dataWindow.XMin || dataWindow.YMax < dataWindow.YMin) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR DataWindow max values must be greater than or equal to min values."); + } + + long width = (long)dataWindow.XMax - dataWindow.XMin + 1; + long height = (long)dataWindow.YMax - dataWindow.YMin + 1; + + // Decoding stages each row as four color planes, so the width must be bounded + // before later width * 4 buffer sizing can overflow. + if (width > int.MaxValue / 4 || height > int.MaxValue) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR DataWindow dimensions exceed the maximum allowed size."); + } + + this.Width = (int)width; + this.Height = (int)height; 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 offsetTableByteCount = chunkCount * sizeof(ulong); + + // The scanline offset table sits between the header and pixel chunks; proving it + // fits in the stream keeps all later chunk offsets on the pixel-data side. + if (stream.Position > stream.Length || offsetTableByteCount > stream.Length - stream.Position) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk offset table is outside the bounds of the stream."); + } + + this.MinimumChunkOffset = stream.Position + offsetTableByteCount; this.PixelType = this.ValidateChannels(); this.ImageDataType = this.DetermineImageDataType(); @@ -866,6 +920,19 @@ internal sealed class ExrDecoderCore : ImageDecoderCore _ => false, }; + /// + /// Validates a scanline chunk offset read from the EXR offset table. + /// + /// The chunk offset to validate. + /// The stream containing the image data. + private void ValidateChunkOffset(ulong chunkOffset, BufferedReadStream stream) + { + if (chunkOffset < (ulong)this.MinimumChunkOffset || chunkOffset >= (ulong)stream.Length) + { + ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk offset is outside the bounds of the stream."); + } + } + /// /// Determines whether this image has alpha channel. /// diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index e2750d897..81b7c4da1 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -170,9 +170,13 @@ internal sealed class ExrEncoderCore CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); + ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; + ulong bytesPerBlock = bytesPerRow * rowsPerBlock; + if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue) + { + throw new ImageFormatException("Image is too large to encode in EXR format."); + } using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); @@ -181,7 +185,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width); ulong[] rowOffsets = new ulong[height]; for (uint y = 0; y < height; y += rowsPerBlock) @@ -262,9 +266,13 @@ internal sealed class ExrEncoderCore CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); + ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); - uint bytesPerBlock = bytesPerRow * rowsPerBlock; + ulong bytesPerBlock = bytesPerRow * rowsPerBlock; + if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue) + { + throw new ImageFormatException("Image is too large to encode in EXR format."); + } using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean); using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean); @@ -273,7 +281,7 @@ internal sealed class ExrEncoderCore Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); - using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width); + using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width); Rgba128 rgb = default; ulong[] rowOffsets = new ulong[height]; diff --git a/src/ImageSharp/Formats/Exr/ExrUtils.cs b/src/ImageSharp/Formats/Exr/ExrUtils.cs index 386210b81..532950daa 100644 --- a/src/ImageSharp/Formats/Exr/ExrUtils.cs +++ b/src/ImageSharp/Formats/Exr/ExrUtils.cs @@ -13,9 +13,9 @@ internal static class ExrUtils /// The image channels array. /// The width in pixels of a row. /// The number of bytes per row. - public static uint CalculateBytesPerRow(IList channels, uint width) + public static ulong CalculateBytesPerRow(IList channels, uint width) { - uint bytesPerRow = 0; + ulong bytesPerRow = 0; foreach (ExrChannelInfo channelInfo in channels) { if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) @@ -26,11 +26,11 @@ internal static class ExrUtils { if (channelInfo.PixelType == ExrPixelType.Half) { - bytesPerRow += 2 * width; + bytesPerRow += 2UL * width; } else { - bytesPerRow += 4 * width; + bytesPerRow += 4UL * width; } } } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs new file mode 100644 index 000000000..2d59c0235 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs @@ -0,0 +1,274 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +/// +/// Security regression tests for the EXR decoder (Findings EXR-1, EXR-2, EXR-3). +/// The EXR decoder was merged to main but not yet included in a tagged NuGet release. +/// Each test demonstrates a crafted-input crash present in the unfixed code. +/// +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrDecoderSecurityTests +{ + /// + /// EXR-1 — EXR DataWindow Integer Overflow Produces Negative Image Dimensions (DoS) + /// + /// Width and Height are computed from attacker-controlled DataWindow attributes + /// using unchecked int subtraction: + /// this.Width = XMax - XMin + 1 // overflows to -2147483647 + /// + /// The negative Width is then passed to the Image<TPixel> constructor, which calls + /// Guard.MustBeGreaterThan(width, 0) → ArgumentOutOfRangeException. + /// + /// After a fix this should throw InvalidImageContentException instead. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 600–601 + /// + [Fact] + public void Decode_DataWindowOverflow_NegativeWidth_Throws() + { + // XMin = -1073741825, XMax = 1073741823 + // Width = 1073741823 - (-1073741825) + 1 = 2^31 + 1 → wraps to -2147483647 + byte[] data = BuildMinimalExr(xMin: -1073741825, yMin: 0, xMax: 1073741823, yMax: 0); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + /// + /// EXR-2 — EXR Row Offset Table Unvalidated Seek (DoS) + /// + /// Row offsets are read from the file and used unconditionally to seek the stream: + /// ulong rowOffset = this.ReadUnsignedLong(stream); + /// stream.Position = (long)rowOffset; // no bounds check + /// + /// A crafted offset of 0xFFFFFFFFFFFFFFFF casts to −1 as long, causing + /// an ArgumentOutOfRangeException when setting stream.Position. + /// + /// After a fix this should throw InvalidImageContentException instead. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 170–175 (scanline) + /// lines 243–248 (tile) + /// + [Fact] + public void Decode_CraftedRowOffsets_OutOfBounds_Throws() + { + // Valid 2×2 image (XMin=0,YMin=0,XMax=1,YMax=1 → Width=2,Height=2). + // Row offset table immediately follows the header null byte: + // 2 rows × 8 bytes each, all set to 0xFFFFFFFFFFFFFFFF. + byte[] invalidOffsets = new byte[16]; + BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets, 0xFFFFFFFFFFFFFFFF); + BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets.AsSpan(8), 0xFFFFFFFFFFFFFFFF); + + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: invalidOffsets); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_CraftedRowOffsets_IntoHeader_Throws() + { + // Offset 0 points back into the EXR file header and must be rejected + // before the decoder seeks to attacker-controlled non-pixel data. + byte[] headerOffsets = new byte[16]; + + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: headerOffsets); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_CraftedRowOffsets_IntoOffsetTable_Throws() + { + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: new byte[16]); + + // Point the first row offset at the second row offset entry. + BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(data.Length - 16), (ulong)(data.Length - 8)); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + /// + /// EXR-3 — Oversized EXR RGBA row sizing is rejected as invalid image content. + /// + /// With 4 RGBA HALF channels and Width = 2^29, the decoded row staging and + /// bytes-per-row arithmetic both exceed the supported buffer sizing limits. + /// The decoder must reject this as InvalidImageContentException before any allocation. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 142–150, 215–223 + /// src/ImageSharp/Formats/Exr/ExrUtils.cs CalculateBytesPerRow + /// + [Fact] + public void Decode_RgbaRowSizingExceedsBufferLimits_Throws() + { + // 4 RGBA HALF channels at this width cannot be represented by the decoder's + // int-sized row staging or block buffers. + byte[] data = BuildMinimalRgbaExr(xMin: 0, yMin: 0, xMax: 536870911, yMax: 0); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_DataWindowWidthExceedsRowBufferLimit_Throws() + { + // A single HALF channel keeps bytesPerBlock below int.MaxValue, but the decoder + // still stages four color planes and must reject widths that overflow width × 4. + byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: int.MaxValue / 4, yMax: 0); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Identify_RowOffsetTableExceedsStream_Throws() + { + // Identify parses the header only, so this verifies the offset table bound is + // validated before scanline decoding reads from the table. + byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: 1, yMax: 1); + + using var stream = new MemoryStream(data); + Assert.Throws( + () => ExrDecoder.Instance.Identify(DecoderOptions.Default, stream)); + } + + // ------------------------------------------------------------------------- + // Helpers: construct minimal valid-enough EXR scanline files. + // + // Required attributes per ParseHeaderAttributes validation: + // channels, compression, dataWindow, displayWindow, + // lineOrder, pixelAspectRatio, screenWindowCenter, screenWindowWidth + // ------------------------------------------------------------------------- + + private static byte[] BuildMinimalExr( + int xMin, int yMin, int xMax, int yMax, + byte[] rowOffsetTableAppend = null) + { + // channels: single "R" HALF channel with xSampling=1, ySampling=1 + // Layout per ReadChannelInfo: name\0 (2) + pixelType (4) + pLinear+reserved (4) + // + xSampling (4) + ySampling (4) = 18 bytes/channel + // + list-null (1) = 19 total + byte[] channelData = + [ + 0x52, 0x00, // "R\0" + 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1) + 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes + 0x01, 0x00, 0x00, 0x00, // xSampling = 1 + 0x01, 0x00, 0x00, 0x00, // ySampling = 1 + 0x00, // channel-list null terminator + ]; + + return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData, rowOffsetTableAppend); + } + + private static byte[] BuildMinimalRgbaExr(int xMin, int yMin, int xMax, int yMax) + { + // 4 HALF channels in alphabetical order (A, B, G, R) per EXR spec. + // 18 bytes per channel × 4 channels + 1 list-null = 73 bytes total. + byte[] channelData = + [ + 0x41, 0x00, // "A\0" + 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1) + 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes + 0x01, 0x00, 0x00, 0x00, // xSampling = 1 + 0x01, 0x00, 0x00, 0x00, // ySampling = 1 + 0x42, 0x00, // "B\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x47, 0x00, // "G\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x52, 0x00, // "R\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, // channel-list null terminator + ]; + + return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData); + } + + private static byte[] BuildExrWithChannels( + int xMin, int yMin, int xMax, int yMax, + byte[] channelData, + byte[] rowOffsetTableAppend = null) + { + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms, System.Text.Encoding.ASCII, leaveOpen: true); + + // Magic (0x01312F76 LE) + version 2, scanline (flags = 0x00) + bw.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01, 0x02, 0x00, 0x00, 0x00 }); + + void WriteAttr(string name, string type, byte[] payload) + { + foreach (char c in name) bw.Write((byte)c); + bw.Write((byte)0); + foreach (char c in type) bw.Write((byte)c); + bw.Write((byte)0); + bw.Write(payload.Length); + bw.Write(payload); + } + + WriteAttr("channels", "chlist", channelData); + + WriteAttr("compression", "compression", [0x00]); // None + + byte[] dw = new byte[16]; + BinaryPrimitives.WriteInt32LittleEndian(dw, xMin); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(4), yMin); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(8), xMax); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(12), yMax); + WriteAttr("dataWindow", "box2i", dw); + + WriteAttr("displayWindow", "box2i", new byte[16]); // all zeros (0,0,0,0) + + WriteAttr("lineOrder", "lineOrder", [0x00]); // IncreasingY + + byte[] aspect = new byte[4]; + BinaryPrimitives.WriteSingleLittleEndian(aspect, 1.0f); + WriteAttr("pixelAspectRatio", "float", aspect); + + WriteAttr("screenWindowCenter", "v2f", new byte[8]); // (0f, 0f) + + byte[] sww = new byte[4]; + BinaryPrimitives.WriteSingleLittleEndian(sww, 1.0f); + WriteAttr("screenWindowWidth", "float", sww); + + bw.Write((byte)0x00); // end-of-header sentinel + + if (rowOffsetTableAppend is not null) + bw.Write(rowOffsetTableAppend); + + return ms.ToArray(); + } +} From ff36e83c740e8049574eb468798151e96a176f12 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 12 May 2026 20:54:12 +1000 Subject: [PATCH 179/240] Add AllocationTrackedMemoryManager and refactor allocators (#3120) * Add AllocationTrackedMemoryManager and refactor allocators * Add AllocationTrackingState and refactor tracking * Cleanup * Address feedback * Introduce ApplyOptions and use in allocators * Propagate allocation tracking to lifetime guards * Address Copilot feedback * Fix multi-buffer group tracking and enforce limits * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix override accesibility --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../AllocationTrackedMemoryManager{T}.cs | 67 ++++++ .../Memory/AllocationTrackingState.cs | 47 ++++ .../Allocators/AllocationOptionsExtensions.cs | 14 +- .../Allocators/Internals/BasicArrayBuffer.cs | 2 +- .../Allocators/Internals/ManagedBufferBase.cs | 2 +- .../RefCountedMemoryLifetimeGuard.cs | 13 ++ .../Internals/SharedArrayPoolBuffer{T}.cs | 7 +- .../Internals/UnmanagedBuffer{T}.cs | 7 +- .../Memory/Allocators/MemoryAllocator.cs | 202 +++++++++++++++++- .../Allocators/MemoryAllocatorOptions.cs | 38 +++- .../Allocators/SimpleGcMemoryAllocator.cs | 33 ++- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 33 ++- .../Allocators/UnmanagedMemoryAllocator.cs | 9 +- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 8 +- .../MemoryGroup{T}.Consumed.cs | 16 +- .../MemoryGroup{T}.Owned.cs | 57 ++++- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 37 +++- .../Memory/InvalidMemoryOperationException.cs | 5 + .../Image/ProcessPixelRowsTestBase.cs | 12 +- .../SimpleGcMemoryAllocatorTests.cs | 44 ++++ ...niformUnmanagedPoolMemoryAllocatorTests.cs | 167 ++++++++++++++- .../MemoryGroupTests.Allocate.cs | 10 +- .../TestUtilities/TestMemoryAllocator.cs | 6 +- 23 files changed, 744 insertions(+), 92 deletions(-) create mode 100644 src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs create mode 100644 src/ImageSharp/Memory/AllocationTrackingState.cs diff --git a/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs new file mode 100644 index 000000000..764bb37e8 --- /dev/null +++ b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory; + +/// +/// Provides the tracked memory-owner contract required by . +/// +/// The element type. +/// +/// Custom allocators implement +/// and return a derived type. The base allocator attaches allocation tracking after the owner has been +/// created so custom implementations cannot forget, duplicate, or mismatch the reservation lifecycle. +/// +public abstract class AllocationTrackedMemoryManager : MemoryManager + where T : struct +{ + private AllocationTrackingState allocationTracking; + + /// + /// Releases resources held by the concrete tracked owner. + /// + /// + /// when the owner is being disposed deterministically; + /// otherwise, . + /// + /// + /// Implementations release their own resources here. Allocation tracking is released by the sealed base + /// dispose path after this method returns. + /// + protected abstract void DisposeCore(bool disposing); + + /// + protected sealed override void Dispose(bool disposing) + { + try + { + this.DisposeCore(disposing); + } + finally + { + this.ReleaseAllocationTracking(); + } + } + + /// + /// Attaches allocation tracking to this owner after allocation has succeeded. + /// + /// The allocator that owns the reservation for this instance. + /// The reserved allocation size, in bytes. + /// + /// calls this exactly once after AllocateCore returns. + /// Derived allocators should not call it themselves; they only construct the concrete owner. + /// + protected internal virtual void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.allocationTracking.Attach(allocator, lengthInBytes); + + /// + /// Releases any tracked allocation bytes associated with this instance. + /// + /// + /// Calling this more than once is safe; only the first call after tracking has been attached releases bytes. + /// + private void ReleaseAllocationTracking() => this.allocationTracking.Release(); +} diff --git a/src/ImageSharp/Memory/AllocationTrackingState.cs b/src/ImageSharp/Memory/AllocationTrackingState.cs new file mode 100644 index 000000000..1e9a632ed --- /dev/null +++ b/src/ImageSharp/Memory/AllocationTrackingState.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Memory; + +/// +/// Tracks a single allocator reservation and releases it exactly once. +/// +/// +/// This type is intended to live as a mutable field on the owning object. It should not be copied +/// after tracking has been attached, because the owner relies on a single shared release state. +/// +internal struct AllocationTrackingState +{ + private MemoryAllocator? allocator; + private long lengthInBytes; + private int released; + + /// + /// Attaches allocator reservation tracking to the current owner. + /// + /// The allocator that owns the reservation. + /// The reserved allocation size, in bytes. + /// + /// Must complete-before the owning object's reference is observable to any other thread. + /// guarantees this by attaching synchronously on the allocating + /// thread before returning the owner; reference publication then provides the release fence + /// that makes these field writes visible to a subsequent on another thread. + /// + internal void Attach(MemoryAllocator allocator, long lengthInBytes) + { + this.allocator = allocator; + this.lengthInBytes = lengthInBytes; + } + + /// + /// Releases the attached allocator reservation once. + /// + internal void Release() + { + if (Interlocked.Exchange(ref this.released, 1) == 0 && this.allocator != null) + { + this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes); + this.allocator = null; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs index 3ead1c5df..986ed7f7c 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs @@ -1,9 +1,19 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Memory; +/// +/// Provides helper methods for working with . +/// internal static class AllocationOptionsExtensions { - public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; + /// + /// Returns a value indicating whether the specified flag is set on the allocation options. + /// + /// The allocation options to inspect. + /// The flag to test for. + /// if is set; otherwise, . + public static bool Has(this AllocationOptions options, AllocationOptions flag) + => (options & flag) == flag; } diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs index 9f34602fb..c22e827a8 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -47,7 +47,7 @@ internal class BasicArrayBuffer : ManagedBufferBase public override Span GetSpan() => this.Array.AsSpan(0, this.Length); /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { } diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs index a6ed797d6..84dd065f5 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// Provides a base class for implementations by implementing pinning logic for adaption. /// /// The element type. -internal abstract class ManagedBufferBase : MemoryManager +internal abstract class ManagedBufferBase : AllocationTrackedMemoryManager where T : struct { private GCHandle pinHandle; diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs index 4a202a96c..b0fe0b649 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// internal abstract class RefCountedMemoryLifetimeGuard : IDisposable { + private AllocationTrackingState allocationTracking; private int refCount = 1; private int disposed; private int released; @@ -38,6 +39,14 @@ internal abstract class RefCountedMemoryLifetimeGuard : IDisposable public void ReleaseRef() => this.ReleaseRef(false); + /// + /// Attaches allocator reservation tracking to this lifetime guard. + /// + /// The allocator that owns the reservation. + /// The reserved allocation size, in bytes. + public void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.allocationTracking.Attach(allocator, lengthInBytes); + public void Dispose() { int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); @@ -69,6 +78,10 @@ internal abstract class RefCountedMemoryLifetimeGuard : IDisposable } this.Release(); + + // Guard-backed resources can be recovered by finalization, so their allocator + // reservation must follow the guard's actual release point instead of the owner object. + this.allocationTracking.Release(); } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 02bdf0f48..97fab1b68 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -13,7 +13,7 @@ internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted where T : struct { private readonly int lengthInBytes; - private LifetimeGuard lifetimeGuard; + private readonly LifetimeGuard lifetimeGuard; public SharedArrayPoolBuffer(int lengthInElements) { @@ -24,7 +24,10 @@ internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted public byte[]? Array { get; private set; } - protected override void Dispose(bool disposing) + protected internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.lifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + + protected override void DisposeCore(bool disposing) { if (this.Array == null) { diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 854b40e0c..3a729cdf2 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// access to unmanaged buffers allocated by . /// /// The element type. -internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted +internal sealed unsafe class UnmanagedBuffer : AllocationTrackedMemoryManager, IRefCounted where T : struct { private readonly int lengthInElements; @@ -31,6 +31,9 @@ internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted public void* Pointer => this.lifetimeGuard.Handle.Pointer; + protected internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.lifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + public override Span GetSpan() { DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); @@ -52,7 +55,7 @@ internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted } /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 8eaf0b6d6..1ca0df1c9 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Memory; public abstract class MemoryAllocator { private const int OneGigabyte = 1 << 30; + private long accumulativeAllocatedBytes; /// /// Gets the default platform-specific global instance that @@ -23,9 +24,34 @@ public abstract class MemoryAllocator /// public static MemoryAllocator Default { get; } = Create(); - internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; + /// + /// Gets the maximum number of bytes that can be allocated by a memory group. + /// + /// + /// The allocation limit is determined by the process architecture: 4 GB for 64-bit processes and + /// 1 GB for 32-bit processes. + /// + internal long MemoryGroupAllocationLimitBytes { get; private protected set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; - internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; + /// + /// Gets the maximum accumulative size, in bytes, of all active allocations made through this allocator instance. + /// + /// + /// Defaults to , effectively imposing no limit on the accumulative total. + /// When set, this provides a safeguard against excessive memory consumption by capping the combined size of + /// outstanding allocations issued by this instance.
+ /// When the accumulative size of active allocations exceeds this limit, an will be thrown to + /// prevent further allocations and signal that the limit has been breached. + ///
+ internal long AccumulativeAllocationLimitBytes { get; private protected set; } = long.MaxValue; + + /// + /// Gets the maximum size, in bytes, that can be allocated for a single buffer. + /// + /// + /// The single buffer allocation limit is set to 1 GB by default. + /// + internal int SingleBufferAllocationLimitBytes { get; private protected set; } = OneGigabyte; /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. @@ -47,13 +73,26 @@ public abstract class MemoryAllocator public static MemoryAllocator Create(MemoryAllocatorOptions options) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes); + allocator.ApplyOptions(options); + return allocator; + } + + /// + /// Applies the supplied to this instance. + /// + /// The options to apply. Properties left as are ignored. + private protected void ApplyOptions(MemoryAllocatorOptions options) + { if (options.AllocationLimitMegabytes.HasValue) { - allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; - allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes); + this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; + this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes); } - return allocator; + if (options.AccumulativeAllocationLimitMegabytes.HasValue) + { + this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; + } } /// @@ -63,15 +102,60 @@ public abstract class MemoryAllocator /// Size of the buffer to allocate. /// The allocation options. /// A buffer of values of type . - /// When length is zero or negative. - /// When length is over the capacity of the allocator. - public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + /// When length is negative or over the capacity of the allocator. + public IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + { + long lengthInBytes = this.GetValidatedAllocationLengthInBytes(length); + bool shouldTrack = this.AccumulativeAllocationLimitBytes != long.MaxValue && lengthInBytes != 0; + if (shouldTrack) + { + this.ReserveAllocation(lengthInBytes); + } + + try + { + AllocationTrackedMemoryManager owner = this.AllocateCore(length, options); + if (shouldTrack) + { + owner.AttachAllocationTracking(this, lengthInBytes); + } + + return owner; + } + catch + { + if (shouldTrack) + { + this.ReleaseAccumulatedBytes(lengthInBytes); + } + + throw; + } + } + + /// + /// Allocates a tracked memory owner for . + /// + /// Type of the data stored in the buffer. + /// Size of the buffer to allocate. + /// The allocation options. + /// A tracked memory owner of values of type . + /// + /// Implementations should only allocate and initialize the concrete owner. The base allocator + /// reserves bytes, attaches tracking to the returned owner, and releases the reservation if allocation fails. + /// + protected abstract AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) where T : struct; /// /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. /// + /// + /// This does not dispose active allocations; callers are responsible for disposing all + /// instances to release memory. + /// public virtual void ReleaseRetainedResources() { } @@ -102,11 +186,109 @@ public abstract class MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); } - // Cast to long is safe because we already checked that the total length is within the limit. - return this.AllocateGroupCore(totalLength, (long)totalLengthInBytes, bufferAlignment, options); + long totalLengthInBytesLong = (long)totalLengthInBytes; + bool shouldTrack = this.AccumulativeAllocationLimitBytes != long.MaxValue && totalLengthInBytesLong != 0; + if (shouldTrack) + { + this.ReserveAllocation(totalLengthInBytesLong); + } + + try + { + MemoryGroup group = this.AllocateGroupCore(totalLength, totalLengthInBytesLong, bufferAlignment, options); + if (shouldTrack) + { + group.AttachAllocationTracking(this, totalLengthInBytesLong); + } + + return group; + } + catch + { + if (shouldTrack) + { + this.ReleaseAccumulatedBytes(totalLengthInBytesLong); + } + + throw; + } } internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) where T : struct => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); + + /// + /// Allocates a single segment for construction. + /// + /// Type of the data stored in the buffer. + /// Size of the segment to allocate. + /// The allocation options. + /// A segment owner for the requested buffer length. + /// + /// The default implementation validates the segment size then calls + /// directly so group construction can reserve and release the total allocation once. + /// + internal virtual IMemoryOwner AllocateGroupBuffer(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + { + _ = this.GetValidatedAllocationLengthInBytes(length); + return this.AllocateCore(length, options); + } + + /// + /// Returns the validated allocation length in bytes. + /// + /// Type of the data stored in the buffer. + /// Size of the buffer to allocate. + /// The allocation length in bytes. + private long GetValidatedAllocationLengthInBytes(int length) + where T : struct + { + if (length < 0) + { + InvalidMemoryOperationException.ThrowNegativeAllocationException(length); + } + + ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); + if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) + { + InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); + } + + return (long)lengthInBytes; + } + + /// + /// Reserves accumulative allocation bytes before creating the underlying buffer. + /// + /// The number of bytes to reserve. + private void ReserveAllocation(long lengthInBytes) + { + if (lengthInBytes <= 0) + { + return; + } + + long total = Interlocked.Add(ref this.accumulativeAllocatedBytes, lengthInBytes); + if (total > this.AccumulativeAllocationLimitBytes) + { + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + InvalidMemoryOperationException.ThrowAccumulativeAllocationOverLimitException(lengthInBytes, total, this.AccumulativeAllocationLimitBytes); + } + } + + /// + /// Releases accumulative allocation bytes previously tracked by this allocator. + /// + /// The number of bytes to release. + internal void ReleaseAccumulatedBytes(long lengthInBytes) + { + if (lengthInBytes <= 0) + { + return; + } + + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index d9ba62c1e..41a5ea5ee 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -10,6 +10,7 @@ public struct MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; private int? allocationLimitMegabytes; + private int? accumulativeAllocationLimitMegabytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -17,7 +18,7 @@ public struct MemoryAllocatorOptions /// public int? MaximumPoolSizeMegabytes { - get => this.maximumPoolSizeMegabytes; + readonly get => this.maximumPoolSizeMegabytes; set { if (value.HasValue) @@ -35,15 +36,48 @@ public struct MemoryAllocatorOptions /// public int? AllocationLimitMegabytes { - get => this.allocationLimitMegabytes; + readonly get => this.allocationLimitMegabytes; set { if (value.HasValue) { Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes)); + if (this.AccumulativeAllocationLimitMegabytes.HasValue) + { + Guard.MustBeLessThanOrEqualTo( + value.Value, + this.AccumulativeAllocationLimitMegabytes.Value, + nameof(this.AllocationLimitMegabytes)); + } } this.allocationLimitMegabytes = value; } } + + /// + /// Gets or sets a value defining the maximum accumulative size, in Megabytes, of all active allocations made + /// through the created instance. + /// (the default) imposes no limit on the accumulative total. + /// + public int? AccumulativeAllocationLimitMegabytes + { + readonly get => this.accumulativeAllocationLimitMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AccumulativeAllocationLimitMegabytes)); + if (this.AllocationLimitMegabytes.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo( + value.Value, + this.AllocationLimitMegabytes.Value, + nameof(this.AccumulativeAllocationLimitMegabytes)); + } + } + + this.accumulativeAllocationLimitMegabytes = value; + } + } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 675afe8b9..5d183fa44 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory; @@ -12,23 +10,24 @@ namespace SixLabors.ImageSharp.Memory; /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { + /// + /// Initializes a new instance of the class with default limits. + /// + public SimpleGcMemoryAllocator() + : this(default) + { + } + + /// + /// Initializes a new instance of the class with custom limits. + /// + /// The to apply. + public SimpleGcMemoryAllocator(MemoryAllocatorOptions options) => this.ApplyOptions(options); + /// protected internal override int GetBufferCapacityInBytes() => int.MaxValue; /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - return new BasicArrayBuffer(new T[length]); - } + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + => new BasicArrayBuffer(new T[length]); } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index d5cd329f1..cfffc679a 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; @@ -71,26 +70,25 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); } + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes, + MemoryAllocatorOptions options) + : this(sharedArrayPoolThresholdInBytes, poolBufferSizeInBytes, maxPoolSizeInBytes, unmanagedBufferSizeInBytes) + => this.ApplyOptions(options); + /// protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; /// - public override IMemoryOwner Allocate( + protected override AllocationTrackedMemoryManager AllocateCore( int length, AllocationOptions options = AllocationOptions.None) { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes) + int lengthInBytes = length * Unsafe.SizeOf(); + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) { SharedArrayPoolBuffer buffer = new(length); if (options.Has(AllocationOptions.Clean)) @@ -101,17 +99,16 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato return buffer; } - if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes) + if (lengthInBytes <= this.poolBufferSizeInBytes) { UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return buffer; + return this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); } } - return this.nonPoolAllocator.Allocate(length, options); + return UnmanagedMemoryAllocator.AllocateBuffer(length, options); } /// diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index daf1a7992..eb52da7c0 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -18,7 +18,14 @@ internal class UnmanagedMemoryAllocator : MemoryAllocator protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + => AllocateBuffer(length, options); + + // The pooled allocator uses this internal entry point when it needs a raw unmanaged owner without + // nesting another allocator-level reservation cycle around the fallback allocation. + internal static UnmanagedBuffer AllocateBuffer(int length, AllocationOptions options = AllocationOptions.None) + where T : struct { UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); if (options.Has(AllocationOptions.Clean)) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 7e9719ea7..03f26aab0 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -15,12 +15,12 @@ public interface IMemoryGroup : IReadOnlyList> /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. ///
- int BufferLength { get; } + public int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - long TotalLength { get; } + public long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. @@ -29,7 +29,7 @@ public interface IMemoryGroup : IReadOnlyList> /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces /// the image buffers internally. /// - bool IsValid { get; } + public bool IsValid { get; } /// /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current @@ -39,5 +39,5 @@ public interface IMemoryGroup : IReadOnlyList> /// implementation, which is still available when casting to one of the underlying interfaces. /// /// A new instance mapping the current values in use. - new MemoryGroupEnumerator GetEnumerator(); + public new MemoryGroupEnumerator GetEnumerator(); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 950e2a019..75e93ce7f 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -31,23 +31,23 @@ internal abstract partial class MemoryGroup /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } + public override MemoryGroupEnumerator GetEnumerator() => new(this); /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* The runtime sees the Array class as if it implemented the * type-generic collection interfaces explicitly, so here we * can just cast the source array to IList> (or to * an equivalent type), and invoke the generic GetEnumerator * method directly from that interface reference. This saves * having to create our own iterator block here. */ - return ((IList>)this.source).GetEnumerator(); - } + => ((IList>)this.source).GetEnumerator(); - public override void Dispose() => this.View.Invalidate(); + public override void Dispose() + { + this.View.Invalidate(); + this.ReleaseAllocationTracking(); + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index af896ee0e..c4b22f156 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -60,6 +60,58 @@ internal abstract partial class MemoryGroup } } + internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + { + if (this.groupLifetimeGuard != null) + { + // Pool-owned multi-buffer groups recover leaked handles through the group guard finalizer. + this.groupLifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + + IMemoryOwner[]? memoryOwners = this.memoryOwners; + if (memoryOwners?.Length == 1 && memoryOwners[0] is AllocationTrackedMemoryManager trackedOwner) + { + // Single-buffer groups should release tracking with the buffer owner when that owner has + // a more precise lifetime, such as an existing pooled-resource finalizer. + trackedOwner.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + + if (memoryOwners?.Length > 1) + { + foreach (IMemoryOwner memoryOwner in memoryOwners) + { + if (memoryOwner is not AllocationTrackedMemoryManager) + { + // Splitting is only valid when every segment can own its reservation. A single + // untracked segment makes the whole group ineligible, and this preflight has + // not attached anything yet, so the entire group can fall back immediately. + base.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + } + + // Non-pool multi-buffer groups have no group-level finalizer, so each segment carries + // its own share of the reservation through the segment owner or its lifetime guard. + long remainingLengthInBytes = lengthInBytes; + int lastOwnerIndex = memoryOwners.Length - 1; + for (int i = 0; i < lastOwnerIndex; i++) + { + trackedOwner = (AllocationTrackedMemoryManager)memoryOwners[i]; + long ownerLengthInBytes = (long)trackedOwner.Memory.Length * Unsafe.SizeOf(); + trackedOwner.AttachAllocationTracking(allocator, ownerLengthInBytes); + remainingLengthInBytes -= ownerLengthInBytes; + } + + trackedOwner = (AllocationTrackedMemoryManager)memoryOwners[lastOwnerIndex]; + trackedOwner.AttachAllocationTracking(allocator, remainingLengthInBytes); + return; + } + + base.AttachAllocationTracking(allocator, lengthInBytes); + } + private static IMemoryOwner[] CreateBuffers( UnmanagedMemoryHandle[] pooledBuffers, int bufferLength, @@ -73,8 +125,8 @@ internal abstract partial class MemoryGroup result[i] = currentBuffer; } - ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); - result[result.Length - 1] = lastBuffer; + ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[^1], sizeOfLastBuffer, options); + result[^1] = lastBuffer; return result; } @@ -155,6 +207,7 @@ internal abstract partial class MemoryGroup } } + this.ReleaseAllocationTracking(); this.memoryOwners = null; this.IsValid = false; this.groupLifetimeGuard = null; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 6dd99fcb0..e0b9bca5e 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -21,6 +21,7 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable { private static readonly int ElementSize = Unsafe.SizeOf(); + private AllocationTrackingState allocationTracking; private MemoryGroupSpanCache memoryGroupSpanCache; private MemoryGroup(int bufferLength, long totalLength) @@ -52,16 +53,36 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// public abstract MemoryGroupEnumerator GetEnumerator(); + /// + /// Attaches allocation tracking by specifying the allocator and the length, in bytes, to be tracked. + /// + /// The memory allocator to use for tracking allocations. + /// The length, in bytes, of the memory region to track. Must be greater than or equal to zero. + /// + /// Intended for one-time initialization after the group has been created; callers should avoid changing + /// tracking state concurrently with disposal. + /// + internal virtual void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) => + this.allocationTracking.Attach(allocator, lengthInBytes); + + /// + /// Releases any resources or tracking information associated with allocation tracking for this instance. + /// + /// + /// This method is intended to be called when allocation tracking is no longer needed. It is safe + /// to call multiple times; subsequent calls after the first have no effect, even when called concurrently. + /// + internal void ReleaseAllocationTracking() => this.allocationTracking.Release(); + /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* This method is implemented in each derived class. * Implementing the method here as non-abstract and throwing, * then reimplementing it explicitly in each derived class, is * a workaround for the lack of support for abstract explicit * interface method implementations in C#. */ - throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); - } + => throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); @@ -81,8 +102,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable int bufferAlignmentInElements, AllocationOptions options = AllocationOptions.None) { - int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); Guard.NotNull(allocator, nameof(allocator)); + int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); if (totalLengthInElements < 0) { @@ -97,8 +118,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable if (totalLengthInElements == 0) { - IMemoryOwner[] buffers0 = [allocator.Allocate(0, options)]; - return new Owned(buffers0, 0, 0, true); + IMemoryOwner[] emptyBuffer = [allocator.AllocateGroupBuffer(0, options)]; + return new Owned(emptyBuffer, 0, 0, true); } int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; @@ -123,12 +144,12 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable IMemoryOwner[] buffers = new IMemoryOwner[bufferCount]; for (int i = 0; i < buffers.Length - 1; i++) { - buffers[i] = allocator.Allocate(bufferLength, options); + buffers[i] = allocator.AllocateGroupBuffer(bufferLength, options); } if (bufferCount > 0) { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); + buffers[^1] = allocator.AllocateGroupBuffer(sizeOfLastBuffer, options); } return new Owned(buffers, bufferLength, totalLengthInElements, true); diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs index 81210f13d..724af35e1 100644 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -39,4 +39,9 @@ public class InvalidMemoryOperationException : InvalidOperationException [DoesNotReturn] internal static void ThrowAllocationOverLimitException(ulong length, long limit) => throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={length} that exceeded the limit {limit}."); + + [DoesNotReturn] + internal static void ThrowAccumulativeAllocationOverLimitException(long requestedLength, long totalLength, long limit) => + throw new InvalidMemoryOperationException( + $"Attempted to allocate a buffer of length={requestedLength} that would increase the accumulative allocation size to {totalLength}, exceeding the limit {limit}."); } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index 27e42f84e..a5dfb0d23 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -313,7 +313,15 @@ public abstract class ProcessPixelRowsTestBase protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => - this.buffers.Pop() as IMemoryOwner; + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + { + object buffer = this.buffers.Pop(); + if (buffer is AllocationTrackedMemoryManager trackedBuffer) + { + return trackedBuffer; + } + + throw new InvalidMemoryOperationException("The requested buffer type does not match the mock allocator buffer type."); + } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 0e791c5d9..c33e6d107 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -48,6 +48,50 @@ public class SimpleGcMemoryAllocatorTests } } + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + [StructLayout(LayoutKind.Explicit, Size = 512)] private struct BigStruct { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 1d185a0de..b4593d4d0 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -16,8 +16,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests { public class BufferTests1 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 1024, poolBufferSizeInBytes: 2048, maxPoolSizeInBytes: 2048 * 4, @@ -31,8 +31,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests public class BufferTests2 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 512, poolBufferSizeInBytes: 1024, maxPoolSizeInBytes: 1024 * 4, @@ -179,8 +179,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g1.Dispose(); // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); - IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy1 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); + IntPtr dummy2 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); @@ -323,7 +323,7 @@ public class UniformUnmanagedPoolMemoryAllocatorTests } [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length) + private static void AllocateGroupAndForget(MemoryAllocator allocator, int length) { // Allocate a group and drop the reference without disposing. // The test relies on the group's finalizer to return the rented memory to the pool. @@ -387,8 +387,34 @@ public class UniformUnmanagedPoolMemoryAllocatorTests } } + [Theory] + [InlineData(1)] // SharedArrayPoolBuffer + [InlineData(2)] // UniformUnmanagedMemoryPool buffer + public void Allocate_AccumulativeLimit_Finalization_ReleasesOwnerReservation(int megabytes) + { + RemoteExecutor.Invoke(RunTest, megabytes.ToString(CultureInfo.InvariantCulture)).Dispose(); + + static void RunTest(string megabytesStr) + { + int megabytesInner = int.Parse(megabytesStr, CultureInfo.InvariantCulture); + int length = megabytesInner * (1 << 20); + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = megabytesInner + }); + + AllocateSingleAndForget(allocator, length); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + allocator.Allocate(length).Dispose(); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length) + private static void AllocateSingleAndForget(MemoryAllocator allocator, int length) { // Allocate and intentionally do not dispose. IMemoryOwner g = allocator.Allocate(length); @@ -409,6 +435,30 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g = null; } + [Fact] + public void AllocateGroup_AccumulativeLimit_Finalization_ReleasesGroupReservation() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + const int megabytes = 5; + int length = megabytes * (1 << 20); + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = megabytes + }); + + AllocateGroupAndForget(allocator, length); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + allocator.AllocateGroup(length, 1024).Dispose(); + } + } + [Fact] public void Allocate_OverLimit_ThrowsInvalidMemoryOperationException() { @@ -433,6 +483,107 @@ public class UniformUnmanagedPoolMemoryAllocatorTests Assert.Throws(() => allocator.AllocateGroup(5 * oneMb, 1024)); } + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_NonPoolFallback_TracksOncePerGroup() + { + // Configure the pool with zero capacity so multi-segment requests bypass both the + // single-buffer-from-pool path and MemoryGroup.TryAllocate(pool, ...) and fall + // through to MemoryGroup.Allocate(nonPoolAllocator, ...). The unmanaged segment + // size is small enough that the request must span multiple segments, which is the + // path where per-segment double-counting could regress. + UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( + sharedArrayPoolThresholdInBytes: 64 * 1024, + poolBufferSizeInBytes: 128 * 1024, + maxPoolSizeInBytes: 0, + unmanagedBufferSizeInBytes: 256 * 1024, + new MemoryAllocatorOptions { AccumulativeAllocationLimitMegabytes = 1 }); + + // 768 KB exceeds the pool buffer size, so the request takes the multi-segment + // non-pool fallback (three 256 KB segments). If tracking double-counted (group + // plus each segment), reservation would be 768 KB + 768 KB = 1.5 MB and exceed + // the 1 MB limit on allocation itself. + MemoryGroup g = allocator.AllocateGroup(768 * 1024, 1024); + Assert.True(g.Count > 1, "Test setup must exercise the multi-segment fallback path."); + + // Reservation should be exactly 768 KB; another 512 KB would push to 1.25 MB and throw. + Assert.Throws(() => allocator.Allocate(512 * 1024)); + + g.Dispose(); + + // After disposal the reservation is fully released; a second equivalent group succeeds. + allocator.AllocateGroup(768 * 1024, 1024).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_NonPoolFallback_Finalization_ReleasesGroupReservation() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( + sharedArrayPoolThresholdInBytes: 64 * 1024, + poolBufferSizeInBytes: 128 * 1024, + maxPoolSizeInBytes: 0, + unmanagedBufferSizeInBytes: 256 * 1024, + new MemoryAllocatorOptions { AccumulativeAllocationLimitMegabytes = 1 }); + + // This exercises the non-pool multi-segment fallback, where reservation ownership has + // to follow the finalizable segment guards because the MemoryGroup itself has no finalizer. + AllocateGroupAndForget(allocator, 768 * 1024); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + allocator.AllocateGroup(768 * 1024, 1024).Dispose(); + } + } + [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] public void MemoryAllocator_Create_SetHighLimit() { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 678a089a8..cca2230e6 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -98,7 +98,15 @@ public partial class MemoryGroupTests [InlineData(AllocationOptions.Clean)] public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) { - UniformUnmanagedMemoryPool pool = new(10, 5); + // Disable trimming to avoid buffers being freed between Return and TryAllocate by the + // trim timer or the Gen2 GC callback. + UniformUnmanagedMemoryPool pool = new( + 10, + 5, + new UniformUnmanagedMemoryPool.TrimSettings + { + Rate = 0 + }); UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index a0ff4a466..f9a4c0a6a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -37,7 +37,7 @@ internal class TestMemoryAllocator : MemoryAllocator this.returnLog = new List(); } - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); return new BasicArrayBuffer(array, length, this); @@ -110,7 +110,7 @@ internal class TestMemoryAllocator : MemoryAllocator /// /// Wraps an array as an instance. /// - private class BasicArrayBuffer : MemoryManager + private class BasicArrayBuffer : AllocationTrackedMemoryManager where T : struct { private readonly TestMemoryAllocator allocator; @@ -159,7 +159,7 @@ internal class TestMemoryAllocator : MemoryAllocator } /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { if (disposing) { From 8f2716b567542d7fa1638592f5d964d4ff3aa815 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 May 2026 14:01:08 +1000 Subject: [PATCH 180/240] GIF: background handling & quantizer overflow fix (#3133) * GIF: background handling & quantizer overflow fix * Fix tests --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 6 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 74 ++++-------- src/ImageSharp/Formats/Gif/GifMetadata.cs | 8 +- .../HexadecatreeQuantizer{TPixel}.cs | 25 ++-- tests/ImageSharp.Tests/Issues/Issue_396.cs | 111 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 + ...tColor_WithHexadecatreeQuantizer_rgb32.bmp | 2 +- ...de_8BitColor_WithOctreeQuantizer_rgb32.bmp | 3 - ...Bike_HexadecatreeQuantizer_ErrorDither.png | 4 +- .../Input/Png/issues/Issue_396_dragon.png | 3 + .../Input/Png/issues/Issue_396_hand_1.png | 3 + .../Input/Png/issues/Issue_396_hand_2.png | 3 + 12 files changed, 171 insertions(+), 74 deletions(-) create mode 100644 tests/ImageSharp.Tests/Issues/Issue_396.cs delete mode 100644 tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp create mode 100644 tests/Images/Input/Png/issues/Issue_396_dragon.png create mode 100644 tests/Images/Input/Png/issues/Issue_396_hand_1.png create mode 100644 tests/Images/Input/Png/issues/Issue_396_hand_2.png diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index c087b6681..dc6ce1846 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -990,7 +990,11 @@ internal sealed class GifDecoderCore : ImageDecoderCore byte index = this.logicalScreenDescriptor.BackgroundColorIndex; this.backgroundColorIndex = index; - this.gifMetadata.BackgroundColorIndex = index; + ReadOnlyMemory? globalColorTable = this.gifMetadata.GlobalColorTable; + if (globalColorTable.HasValue && index < globalColorTable.Value.Length) + { + this.gifMetadata.BackgroundColor = globalColorTable.Value.Span[index]; + } } private unsafe struct ScratchBuffer diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 49640fae0..dcba3953d 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -45,14 +45,6 @@ internal sealed class GifEncoderCore /// private readonly IPixelSamplingStrategy pixelSamplingStrategy; - /// - /// The default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when a frame disposal mode is . - /// - private readonly Color? backgroundColor; - /// /// The number of times any animation is repeated. /// @@ -76,7 +68,6 @@ internal sealed class GifEncoderCore this.skipMetadata = encoder.SkipMetadata; this.colorTableMode = encoder.ColorTableMode; this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; - this.backgroundColor = encoder.BackgroundColor; this.repeatCount = encoder.RepeatCount; this.transparentColorMode = encoder.TransparentColorMode; } @@ -113,7 +104,17 @@ internal sealed class GifEncoderCore TransparentColorMode mode = this.transparentColorMode; // Create a new quantizer options instance augmenting the transparent color mode to match the encoder. - QuantizerOptions options = (this.encoder.Quantizer?.Options ?? new QuantizerOptions()).DeepClone(o => o.TransparentColorMode = mode); + QuantizerOptions options = (this.encoder.Quantizer?.Options ?? new QuantizerOptions()).DeepClone(o => + { + o.TransparentColorMode = mode; + + // Animated GIF delta frames can use one padded color-table index as transparency. + // Express that through MaxColors so custom quantizers receive the same budget. + if (image.Frames.Count > 1 && o.MaxColors == QuantizerConstants.MaxColors) + { + o.MaxColors = QuantizerConstants.MaxColors - 1; + } + }); if (globalQuantizer is null) { @@ -145,6 +146,11 @@ internal sealed class GifEncoderCore IPixelSamplingStrategy strategy = this.pixelSamplingStrategy; ImageFrame encodingFrame = image.Frames.RootFrame; + + // This color is encoded as the logical-screen background index and is also + // used when de-duplicating frames that restore to the GIF background. + Color backgroundColor = this.encoder.BackgroundColor ?? gifMetadata.BackgroundColor ?? Color.Transparent; + byte backgroundIndex = 0; if (useGlobalTableForFirstFrame) { using IQuantizer firstFrameQuantizer = globalQuantizer.CreatePixelSpecificQuantizer(this.configuration, options); @@ -158,6 +164,8 @@ internal sealed class GifEncoderCore } quantized = firstFrameQuantizer.QuantizeFrame(encodingFrame, encodingFrame.Bounds); + TPixel backgroundPixel = backgroundColor.ToPixel(); + backgroundIndex = firstFrameQuantizer.GetQuantizedColor(backgroundPixel, out _); } else { @@ -184,8 +192,6 @@ internal sealed class GifEncoderCore frameMetadata.TransparencyIndex = ClampIndex(transparencyIndex); } - byte backgroundIndex = GetBackgroundIndex(quantized, gifMetadata, this.backgroundColor); - // Get the number of bits. int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteLogicalScreenDescriptor(image.Metadata, image.Width, image.Height, backgroundIndex, useGlobalTable, bitDepth, stream); @@ -222,6 +228,7 @@ internal sealed class GifEncoderCore image, globalQuantizer, globalFrameQuantizer, + backgroundColor, transparencyIndex, frameMetadata.DisposalMode, cancellationToken); @@ -253,6 +260,7 @@ internal sealed class GifEncoderCore Image image, IQuantizer globalQuantizer, PaletteQuantizer globalFrameQuantizer, + Color backgroundColor, int globalTransparencyIndex, FrameDisposalMode previousDisposalMode, CancellationToken cancellationToken) @@ -284,6 +292,7 @@ internal sealed class GifEncoderCore globalFrameQuantizer, useLocal, gifMetadata, + backgroundColor, previousDisposalMode); previousFrame = currentFrame; @@ -327,6 +336,7 @@ internal sealed class GifEncoderCore PaletteQuantizer globalFrameQuantizer, bool useLocal, GifFrameMetadata metadata, + Color backgroundColor, FrameDisposalMode previousDisposalMode) where TPixel : unmanaged, IPixel { @@ -347,7 +357,7 @@ internal sealed class GifEncoderCore previous.Metadata.GetGifMetadata().DisposalMode; Color background = !useTransparency && disposalMode == FrameDisposalMode.RestoreToBackground - ? this.backgroundColor ?? Color.Transparent + ? backgroundColor : Color.Transparent; // Deduplicate and quantize the frame capturing only required parts. @@ -534,44 +544,6 @@ internal sealed class GifEncoderCore return index; } - /// - /// Returns the index of the background color in the palette. - /// - /// The current quantized frame. - /// The gif metadata - /// The background color to match. - /// The pixel format. - /// The index of the background color. - private static byte GetBackgroundIndex(IndexedImageFrame? quantized, GifMetadata metadata, Color? background) - where TPixel : unmanaged, IPixel - { - int match = -1; - if (quantized != null) - { - if (background.HasValue) - { - TPixel backgroundPixel = background.Value.ToPixel(); - ReadOnlySpan palette = quantized.Palette.Span; - for (int i = 0; i < palette.Length; i++) - { - if (!backgroundPixel.Equals(palette[i])) - { - continue; - } - - match = i; - break; - } - } - else if (metadata.BackgroundColorIndex < quantized.Palette.Length) - { - match = metadata.BackgroundColorIndex; - } - } - - return ClampIndex(match); - } - /// /// Writes the file header signature and version to the stream. /// diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 978209b23..50a29775d 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -26,7 +26,7 @@ public class GifMetadata : IFormatMetadata { this.RepeatCount = other.RepeatCount; this.ColorTableMode = other.ColorTableMode; - this.BackgroundColorIndex = other.BackgroundColorIndex; + this.BackgroundColor = other.BackgroundColor; if (other.GlobalColorTable?.Length > 0) { @@ -59,10 +59,9 @@ public class GifMetadata : IFormatMetadata public ReadOnlyMemory? GlobalColorTable { get; set; } /// - /// Gets or sets the index at the for the background color. - /// The background color is the color used for those pixels on the screen that are not covered by an image. + /// Gets or sets the background color used for pixels on the screen that are not covered by an image. /// - public byte BackgroundColorIndex { get; set; } + public Color? BackgroundColor { get; set; } /// /// Gets or sets the collection of comments about the graphics, credits, descriptions or any @@ -101,6 +100,7 @@ public class GifMetadata : IFormatMetadata { AnimateRootFrame = true, ColorTableMode = this.ColorTableMode, + BackgroundColor = this.BackgroundColor ?? Color.Transparent, PixelTypeInfo = this.GetPixelTypeInfo(), RepeatCount = this.RepeatCount, }; diff --git a/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs index b5d39d73e..71c1b8457 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs @@ -392,11 +392,11 @@ public struct HexadecatreeQuantizer : IQuantizer internal struct Node { public bool Leaf; - public int PixelCount; - public int Red; - public int Green; - public int Blue; - public int Alpha; + public long PixelCount; + public long Red; + public long Green; + public long Blue; + public long Alpha; public short PaletteIndex; public short NextReducibleIndex; private InlineArray16 children; @@ -510,12 +510,13 @@ public struct HexadecatreeQuantizer : IQuantizer return; } - // Now merge the (presumably reduced) children. - int pixelCount = 0; - int sumRed = 0; - int sumGreen = 0; - int sumBlue = 0; - int sumAlpha = 0; + // Allocation fallback can accumulate samples on an interior node. Seed the merge + // with this node's own sums so reduction preserves those samples with its children. + long pixelCount = this.PixelCount; + long sumRed = this.Red; + long sumGreen = this.Green; + long sumBlue = this.Blue; + long sumAlpha = this.Alpha; Span children = this.Children; for (int i = 0; i < children.Length; i++) @@ -524,7 +525,7 @@ public struct HexadecatreeQuantizer : IQuantizer if (childIndex != -1) { ref Node child = ref tree.Nodes[childIndex]; - int pixels = child.PixelCount; + long pixels = child.PixelCount; sumRed += child.Red; sumGreen += child.Green; sumBlue += child.Blue; diff --git a/tests/ImageSharp.Tests/Issues/Issue_396.cs b/tests/ImageSharp.Tests/Issues/Issue_396.cs new file mode 100644 index 000000000..0d76f0c80 --- /dev/null +++ b/tests/ImageSharp.Tests/Issues/Issue_396.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Tests.Issues; + +// https://github.com/SixLabors/ImageSharp.Drawing/discussions/396 +public class Issue_396 +{ + [Theory] + [WithFile(TestImages.Png.Issue396Dragon, PixelTypes.Rgba32)] + public void GeneratesGifForInspection(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Image[] handImages = + [ + Image.Load(TestFile.Create(TestImages.Png.Issue396Hand1).Bytes), + Image.Load(TestFile.Create(TestImages.Png.Issue396Hand2).Bytes) + ]; + + try + { + using Image inputImage = provider.GetImage(); + using Image outputImage = new(handImages[0].Width, handImages[0].Height); + + const float scaleFactor = 0.6F; + const float squashStepFactor = 0.22F; + + for (int i = 0; i < handImages.Length; i++) + { + using Image frameImage = new(handImages[i].Width, handImages[i].Height); + float squashFactorY = 1 - (i * squashStepFactor); + + frameImage.ProcessPixelRows(accessor => + { + Rgba32 gray = Color.Gray.ToPixel(); + + for (int y = 0; y < accessor.Height; y++) + { + accessor.GetRowSpan(y).Fill(gray); + } + }); + + Rectangle targetRect = new( + (int)((frameImage.Width - (frameImage.Width * scaleFactor)) / 2), + (int)(frameImage.Height * (((1 - scaleFactor) / 2) + (scaleFactor * (1 - squashFactorY)))), + (int)(frameImage.Width * scaleFactor), + (int)(frameImage.Height * scaleFactor * squashFactorY)); + + using Image dragonFrame = inputImage.CloneAs(); + dragonFrame.Mutate(context => context.Resize(targetRect.Size)); + + frameImage.Mutate(context => + { + context.DrawImage(dragonFrame, targetRect.Location, 1F); + context.DrawImage(handImages[i], Point.Empty, 1F); + }); + + ImageFrame outputFrame = outputImage.Frames.AddFrame(frameImage.Frames.RootFrame); + GifFrameMetadata gifFrameMetadata = outputFrame.Metadata.GetGifMetadata(); + gifFrameMetadata.FrameDelay = 40; + gifFrameMetadata.DisposalMode = FrameDisposalMode.RestoreToBackground; + } + + for (int i = handImages.Length - 1; i > 0; i--) + { + outputImage.Frames.AddFrame(outputImage.Frames[i]); + } + + outputImage.Frames.RemoveFrame(0); + GifMetadata gifMetadata = outputImage.Metadata.GetGifMetadata(); + gifMetadata.RepeatCount = 0; + + Assert.Equal((handImages.Length * 2) - 1, outputImage.Frames.Count); + foreach (ImageFrame frame in outputImage.Frames) + { + Assert.Equal(FrameDisposalMode.RestoreToBackground, frame.Metadata.GetGifMetadata().DisposalMode); + } + + outputImage.DebugSaveMultiFrame(provider, "Issue396-source-frames", appendPixelTypeToFileName: false); + provider.Utility.SaveTestOutputFile( + outputImage, + "gif", + new GifEncoder(), + "Issue396-encoded", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + // Save and decode the GIF so the encoder's optimized frame diffs can be inspected separately. + using MemoryStream gifStream = new(); + outputImage.SaveAsGif(gifStream); + gifStream.Position = 0; + + using Image decodedImage = Image.Load(gifStream); + decodedImage.DebugSaveMultiFrame(provider, "Issue396-decoded-frames", appendPixelTypeToFileName: false); + + Assert.Equal(outputImage.Frames.Count, decodedImage.Frames.Count); + } + finally + { + foreach (Image handImage in handImages) + { + handImage.Dispose(); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 2624d1cda..d74db0eeb 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -79,6 +79,9 @@ public static class TestImages public const string AnimatedFrameCount = "Png/animated/issue-animated-frame-count.png"; public const string Issue2666 = "Png/issues/Issue_2666.png"; public const string Issue2882 = "Png/issues/Issue_2882.png"; + public const string Issue396Dragon = "Png/issues/Issue_396_dragon.png"; + public const string Issue396Hand1 = "Png/issues/Issue_396_hand_1.png"; + public const string Issue396Hand2 = "Png/issues/Issue_396_hand_2.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp index f4ae3b9b6..7f79c6f3a 100644 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 +oid sha256:fff91e78a87c2b2db661609dd70af2d7a74e6a0de18924ab2b6272e12c60977d size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp deleted file mode 100644 index f4ae3b9b6..000000000 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 -size 9270 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png index e9782bc07..b17bce9e3 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b380eda5646fe97ee217ef711103001e54ee023fb8d95f7f3bbad19d886130da -size 83702 +oid sha256:822f4ae1d8676e2231a0c938296931be42a74c0a51b4ec63fdf6dcd17d64dc7e +size 83924 diff --git a/tests/Images/Input/Png/issues/Issue_396_dragon.png b/tests/Images/Input/Png/issues/Issue_396_dragon.png new file mode 100644 index 000000000..4e8b2d891 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_dragon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:203aca58e0aa19cbb7ab0820ef55005af830bf4c9bfd1dd4d9967c7e1bc1d51a +size 494160 diff --git a/tests/Images/Input/Png/issues/Issue_396_hand_1.png b/tests/Images/Input/Png/issues/Issue_396_hand_1.png new file mode 100644 index 000000000..1624bce04 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_hand_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ab791784ac46f9e50a63bd255f39ff8af236a922aab3510a29b473bd93bffd5 +size 16295 diff --git a/tests/Images/Input/Png/issues/Issue_396_hand_2.png b/tests/Images/Input/Png/issues/Issue_396_hand_2.png new file mode 100644 index 000000000..d12fa097b --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_hand_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbf54bcef6730b68b18770105a15448f984aaca6910693d4e65106a526af58d8 +size 17756 From 8e6cab957b80a56d29ecfbdc3b8f628d09c16d81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 May 2026 14:06:11 +1000 Subject: [PATCH 181/240] Validate PBM max pixel value --- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 5 +++++ .../ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 989eadda7..5451d3d46 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -155,6 +155,11 @@ internal sealed class PbmDecoderCore : ImageDecoderCore ThrowPrematureEof(); } + if (this.maxPixelValue <= 0 || this.maxPixelValue >= 65536) + { + throw new InvalidImageContentException("Invalid max pixel value."); + } + if (this.maxPixelValue > 255) { this.componentType = PbmComponentType.Short; diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 476538ccd..5c52c9785 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -123,6 +123,19 @@ public class PbmDecoderTests appendPixelTypeToFileName: false); } + [Theory] + [InlineData("P2")] + [InlineData("P3")] + [InlineData("P5")] + [InlineData("P6")] + public void Decode_MaxPixelValueZero_ThrowsInvalidImageContentException(string magic) + { + byte[] bytes = Encoding.ASCII.GetBytes($"{magic} 1 1 0\n0"); + using MemoryStream stream = new(bytes); + + Assert.Throws(() => Image.Load(stream)); + } + [Fact] public void PlainText_PrematureEof() { From 3e843a06a286cb65a126b9777bd6facf04695d21 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Tue, 26 May 2026 15:35:52 +0200 Subject: [PATCH 182/240] Implement CgBI support --- .../Compression/Zlib/ZlibInflateStream.cs | 31 +++++++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 73 ++++++++++++++++++- .../Formats/Png/PngDecoderTests.cs | 26 +++---- tests/ImageSharp.Tests/TestImages.cs | 14 +++- ...reshold-0_PerPixelManhattanThreshold-0.png | 3 + ...reshold-0_PerPixelManhattanThreshold-0.png | 3 + ...reshold-0_PerPixelManhattanThreshold-0.png | 3 + ...reshold-0_PerPixelManhattanThreshold-0.png | 3 + ...reshold-0_PerPixelManhattanThreshold-0.png | 3 + tests/Images/Input/Png/cgbi/clocks.png | 3 + tests/Images/Input/Png/cgbi/colors.png | 3 + tests/Images/Input/Png/cgbi/flecks.png | 3 + tests/Images/Input/Png/cgbi/screen.png | 3 + 13 files changed, 148 insertions(+), 23 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png create mode 100644 tests/Images/Input/Png/cgbi/clocks.png create mode 100644 tests/Images/Input/Png/cgbi/colors.png create mode 100644 tests/Images/Input/Png/cgbi/flecks.png create mode 100644 tests/Images/Input/Png/cgbi/screen.png diff --git a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs index 1d743bf3a..513171b17 100644 --- a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs @@ -52,12 +52,19 @@ internal sealed class ZlibInflateStream : Stream /// private readonly Func getData; + /// + /// When true, the inflated payload is treated as a raw DEFLATE stream with no zlib + /// CMF/FLG header (and no Adler-32 trailer). This is required to decode IDATs in + /// Apple's proprietary CgBI PNG variant. + /// + private readonly bool noHeader; + /// /// Initializes a new instance of the class. /// /// The inner raw stream. public ZlibInflateStream(BufferedReadStream innerStream) - : this(innerStream, GetDataNoOp) + : this(innerStream, GetDataNoOp, noHeader: false) { } @@ -67,9 +74,23 @@ internal sealed class ZlibInflateStream : Stream /// The inner raw stream. /// A delegate to get more data from the inner stream. public ZlibInflateStream(BufferedReadStream innerStream, Func getData) + : this(innerStream, getData, noHeader: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The inner raw stream. + /// A delegate to get more data from the inner stream. + /// + /// When , the payload is treated as raw DEFLATE with no zlib header. + /// + public ZlibInflateStream(BufferedReadStream innerStream, Func getData, bool noHeader) { this.innerStream = innerStream; this.getData = getData; + this.noHeader = noHeader; } /// @@ -210,6 +231,14 @@ internal sealed class ZlibInflateStream : Stream [MemberNotNullWhen(true, nameof(CompressedStream))] private bool InitializeInflateStream(bool isCriticalChunk) { + // Apple CgBI IDATs omit the zlib CMF/FLG header and the Adler-32 trailer, + // wrapping a raw DEFLATE payload directly. Skip the header parsing in that mode. + if (this.noHeader) + { + this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + return true; + } + // Read the zlib header : http://tools.ietf.org/html/rfc1950 // CMF(Compression Method and flags) // This byte is divided into a 4 - bit compression method and a diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 5b9eee116..063c27a9a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -137,6 +137,13 @@ internal sealed class PngDecoderCore : ImageDecoderCore ///
private bool hasImageData; + /// + /// Whether this is an Apple CgBI PNG. CgBI files store IDATs as raw DEFLATE + /// (no zlib header/Adler-32) and pixels as premultiplied BGRA, so they need + /// extra inversion steps to round-trip back to standard PNG semantics. + /// + private bool isCgbi; + /// /// Initializes a new instance of the class. /// @@ -314,7 +321,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore case PngChunkType.End: goto EOF; case PngChunkType.ProprietaryApple: - PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); + this.isCgbi = true; break; } } @@ -517,6 +524,10 @@ internal sealed class PngDecoderCore : ImageDecoderCore case PngChunkType.End: goto EOF; + case PngChunkType.ProprietaryApple: + this.isCgbi = true; + break; + default: if (this.colorMetadataOnly) { @@ -766,7 +777,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using ZlibInflateStream inflateStream = new(this.currentStream, getData); + using ZlibInflateStream inflateStream = new(this.currentStream, getData, noHeader: this.isCgbi); if (!inflateStream.AllocateNewBytes(chunkLength, !this.hasImageData)) { return; @@ -887,6 +898,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; } + if (this.isCgbi) + { + ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + } + this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); this.SwapScanlineBuffers(); currentRow++; @@ -1017,6 +1033,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore break; } + if (this.isCgbi) + { + ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + } + Span rowSpan = imageBuffer.DangerousGetRowSpan(currentRow); this.ProcessInterlacedDefilteredScanline( frameControl, @@ -2470,4 +2491,52 @@ internal sealed class PngDecoderCore : ImageDecoderCore private void SwapScanlineBuffers() => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); + + /// + /// Applies the inverse of Apple's CgBI pixel mangling to a defiltered scanline. + /// CgBI PNGs are emitted by pngcrush -iphone with channel order swapped + /// from RGB(A) to BGR(A) and RGB samples premultiplied by alpha. This converts + /// the bytes back to standard PNG semantics in place so the existing scanline + /// processors can consume them unchanged. CgBI is only emitted for 8-bit + /// truecolor (with or without alpha); other color types are left alone. + /// + /// + /// See https://theapplewiki.com/wiki/PNG_CgBI_Format + /// + /// The defiltered pixel bytes (without the leading filter byte). + /// The PNG color type from IHDR. + private static void ApplyCgbiTransform(Span scanline, PngColorType colorType) + { + if (colorType == PngColorType.RgbWithAlpha) + { + for (int i = 0; i + 3 < scanline.Length; i += 4) + { + byte b = scanline[i]; + byte g = scanline[i + 1]; + byte r = scanline[i + 2]; + byte a = scanline[i + 3]; + + if (a is not 0 and not 255) + { + // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) + int half = a >> 1; + r = (byte)Math.Min(255, ((r * 255) + half) / a); + g = (byte)Math.Min(255, ((g * 255) + half) / a); + b = (byte)Math.Min(255, ((b * 255) + half) / a); + } + + scanline[i] = r; + scanline[i + 1] = g; + scanline[i + 2] = b; + scanline[i + 3] = a; + } + } + else if (colorType == PngColorType.Rgb) + { + for (int i = 0; i + 2 < scanline.Length; i += 3) + { + (scanline[i], scanline[i + 2]) = (scanline[i + 2], scanline[i]); + } + } + } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 803a12b03..88018709c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -714,26 +714,18 @@ public partial class PngDecoderTests Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToPixel().A < 255); } - // https://github.com/SixLabors/ImageSharp/issues/410 [Theory] - [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] - public void Issue410_MalformedApplePng(TestImageProvider provider) + [WithFile(TestImages.Png.Cgbi.Issue410, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Colors, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Clocks, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Flecks, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Screen, PixelTypes.Rgba32)] + public void Decode_AppleCgBI(TestImageProvider provider) where TPixel : unmanaged, IPixel { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png); - } - }); - Assert.NotNull(ex); - Assert.Contains("Proprietary Apple PNG detected!", ex.Message); + using Image image = provider.GetImage(PngDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider, ImageComparer.Exact); } [Theory] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 2624d1cda..fee6fce37 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -177,6 +177,17 @@ public static class TestImages public const string PerceptualcLUTOnly = "Png/icc-profiles/Perceptual-cLUT-only.png"; } + public static class Cgbi + { + public const string Colors = "Png/cgbi/colors.png"; + public const string Clocks = "Png/cgbi/clocks.png"; + public const string Flecks = "Png/cgbi/flecks.png"; + public const string Screen = "Png/cgbi/screen.png"; + + // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 + public const string Issue410 = "Png/issues/Issue_410.png"; + } + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; @@ -199,9 +210,6 @@ public static class TestImages // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; - // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 - public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; - // Bad bit depth. public const string BitDepthZero = "Png/xd0n2c08.png"; public const string BitDepthThree = "Png/xd3n2c08.png"; diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..020facf41 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:511cb90e72fcb837e4c9a31561a3c914f5201452d4ca63502cb5219cb4dc42be +size 619 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..0f184812b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dc73f1b4435a26125d910f005b5df7a540168a954f44c01a8e46df201adb1b6 +size 335851 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..74647f04f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cdcc80a8c662c50d2f72ad8e123d595c6e80394538c05666b8d3531d651e71a +size 11270 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..06873607a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91bbb6c87f128920d4384f3be2e85ecd176e49a7a5166c6c6aa584e60d8131ed +size 204053 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..c3a61bde8 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00bb4c7b345389f5d95252c19d70fc2b654c4f0198e6a704b603da92b78e9a0a +size 102982 diff --git a/tests/Images/Input/Png/cgbi/clocks.png b/tests/Images/Input/Png/cgbi/clocks.png new file mode 100644 index 000000000..d7cf18367 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/clocks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc462d8c2697060cde9a2e975ffb828d822ee3b0d4d12e3c5f081114176c036b +size 389981 diff --git a/tests/Images/Input/Png/cgbi/colors.png b/tests/Images/Input/Png/cgbi/colors.png new file mode 100644 index 000000000..9b3b371bd --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f34436f755e3c6d15341f29c992331045ae16ad413144ca798ede5c085c8e6a +size 12853 diff --git a/tests/Images/Input/Png/cgbi/flecks.png b/tests/Images/Input/Png/cgbi/flecks.png new file mode 100644 index 000000000..625c82e45 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/flecks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:314a5a52996953c0c12862262b4ff3fc191d8b6f2b7bb3853cdcea3267a4142d +size 212163 diff --git a/tests/Images/Input/Png/cgbi/screen.png b/tests/Images/Input/Png/cgbi/screen.png new file mode 100644 index 000000000..57eca3d46 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e9bfbac37a57b71fa27b38b21314bd49dbe1cb19a2eb9d0f272ec7be3b72a33 +size 94834 From 4f85600cb8d0d9e461d6647ab754594cde8fc4b7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Tue, 26 May 2026 15:43:09 +0200 Subject: [PATCH 183/240] Improve ApplyCgbiTransform --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 31 ++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 063c27a9a..dcd9ffd6c 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -2509,33 +2509,34 @@ internal sealed class PngDecoderCore : ImageDecoderCore { if (colorType == PngColorType.RgbWithAlpha) { - for (int i = 0; i + 3 < scanline.Length; i += 4) + Span pixels = MemoryMarshal.Cast(scanline); + for (int i = 0; i < pixels.Length; i++) { - byte b = scanline[i]; - byte g = scanline[i + 1]; - byte r = scanline[i + 2]; - byte a = scanline[i + 3]; + ref Rgba32 p = ref pixels[i]; + byte r = p.B; + byte g = p.G; + byte b = p.R; + byte a = p.A; - if (a is not 0 and not 255) + if (a is not 0 and not byte.MaxValue) { // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) int half = a >> 1; - r = (byte)Math.Min(255, ((r * 255) + half) / a); - g = (byte)Math.Min(255, ((g * 255) + half) / a); - b = (byte)Math.Min(255, ((b * 255) + half) / a); + r = (byte)Math.Min(byte.MaxValue, ((r * byte.MaxValue) + half) / a); + g = (byte)Math.Min(byte.MaxValue, ((g * byte.MaxValue) + half) / a); + b = (byte)Math.Min(byte.MaxValue, ((b * byte.MaxValue) + half) / a); } - scanline[i] = r; - scanline[i + 1] = g; - scanline[i + 2] = b; - scanline[i + 3] = a; + p = new Rgba32(r, g, b, a); } } else if (colorType == PngColorType.Rgb) { - for (int i = 0; i + 2 < scanline.Length; i += 3) + Span pixels = MemoryMarshal.Cast(scanline); + for (int i = 0; i < pixels.Length; i++) { - (scanline[i], scanline[i + 2]) = (scanline[i + 2], scanline[i]); + ref Rgb24 p = ref pixels[i]; + (p.R, p.B) = (p.B, p.R); } } } From 70a49b1d9d7a7086ffa5adb0bfcd057f7867142c Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Tue, 26 May 2026 15:50:58 +0200 Subject: [PATCH 184/240] Add tests for Identify --- .../Formats/Png/PngDecoderTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 88018709c..c1ba56f3b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -728,6 +728,22 @@ public partial class PngDecoderTests image.CompareToReferenceOutput(provider, ImageComparer.Exact); } + [Theory] + [InlineData(TestImages.Png.Cgbi.Colors, 120, 120)] + [InlineData(TestImages.Png.Cgbi.Issue410, 42, 26)] + public void Identify_AppleCgBI(string imagePath, int expectedWidth, int expectedHeight) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(PngFormat.Instance, imageInfo.Metadata.DecodedImageFormat); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } + [Theory] [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] From 5d206ec10f9611603e0e8566ac11725bcb5d6c1f Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Tue, 26 May 2026 17:41:21 +0200 Subject: [PATCH 185/240] Add test data for RGB24 --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 3 ++- ...4_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png | 3 +++ ...2_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png | 3 --- tests/Images/Input/Png/cgbi/flecks.png | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png delete mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index c1ba56f3b..d3a699c49 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -718,8 +718,8 @@ public partial class PngDecoderTests [WithFile(TestImages.Png.Cgbi.Issue410, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Cgbi.Colors, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Cgbi.Clocks, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Cgbi.Flecks, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Cgbi.Screen, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Flecks, PixelTypes.Rgb24)] public void Decode_AppleCgBI(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -731,6 +731,7 @@ public partial class PngDecoderTests [Theory] [InlineData(TestImages.Png.Cgbi.Colors, 120, 120)] [InlineData(TestImages.Png.Cgbi.Issue410, 42, 26)] + [InlineData(TestImages.Png.Cgbi.Flecks, 510, 512)] public void Identify_AppleCgBI(string imagePath, int expectedWidth, int expectedHeight) { TestFile testFile = TestFile.Create(imagePath); diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..a180f0118 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10ee142fc1d3638ebe53fdd21c0a4c53008801befcc5e986051b796561cae887 +size 237676 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png deleted file mode 100644 index 06873607a..000000000 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91bbb6c87f128920d4384f3be2e85ecd176e49a7a5166c6c6aa584e60d8131ed -size 204053 diff --git a/tests/Images/Input/Png/cgbi/flecks.png b/tests/Images/Input/Png/cgbi/flecks.png index 625c82e45..640044229 100644 --- a/tests/Images/Input/Png/cgbi/flecks.png +++ b/tests/Images/Input/Png/cgbi/flecks.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:314a5a52996953c0c12862262b4ff3fc191d8b6f2b7bb3853cdcea3267a4142d -size 212163 +oid sha256:6be7b478594ba5e4d37bc135c881c0a16cf1c804fece5440bab997c7b69182f1 +size 187703 From a3e9cc6fd4a097e333c014d592747c5a80dd4371 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 May 2026 13:34:17 +1000 Subject: [PATCH 186/240] Fix in-place shuffles and vectorize PNG CgBI transform --- .../Common/Helpers/Shuffle/IPad3Shuffle4.cs | 46 ++- .../Common/Helpers/Shuffle/IShuffle3.cs | 16 +- .../Common/Helpers/Shuffle/IShuffle4.cs | 14 +- .../Common/Helpers/Shuffle/IShuffle4Slice3.cs | 34 +- .../Common/Helpers/SimdUtils.Shuffle.cs | 14 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 300 ++++++++++++++++-- .../Formats/Png/PngDecoderTests.cs | 28 +- 7 files changed, 390 insertions(+), 62 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs index 0f282c7f9..bbe14b099 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs @@ -31,19 +31,23 @@ internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); - Span temp = stackalloc byte[4]; - ref byte t = ref MemoryMarshal.GetReference(temp); - ref uint tu = ref Unsafe.As(ref t); - for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4) { - ref byte s = ref Unsafe.Add(ref sBase, i); - tu = Unsafe.As(ref s) | 0xFF000000; - - Unsafe.Add(ref dBase, j + 0) = Unsafe.Add(ref t, p0); - Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1); - Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2); - Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3); + // Expanding 3-byte pixels to 4 bytes can overwrite the next source + // triplet when spans overlap. Assemble the padded pixel first, then + // shuffle from the staged uint. + uint packed = + Unsafe.Add(ref sBase, i + 0u) | + ((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | + ((uint)Unsafe.Add(ref sBase, i + 2u) << 16) | + 0xFF000000; + + ref byte pBase = ref Unsafe.As(ref packed); + + Unsafe.Add(ref dBase, j + 0u) = Unsafe.Add(ref pBase, p0); + Unsafe.Add(ref dBase, j + 1u) = Unsafe.Add(ref pBase, p1); + Unsafe.Add(ref dBase, j + 2u) = Unsafe.Add(ref pBase, p2); + Unsafe.Add(ref dBase, j + 3u) = Unsafe.Add(ref pBase, p3); } } } @@ -65,7 +69,12 @@ internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) { - Unsafe.As(ref dBase) = Unsafe.As(ref sBase) | 0xFF000000; + // The fast scalar path reads one extra byte past the source triplet. + // Keep that widened read in a local before writing the expanded pixel + // so overlapping destinations cannot change what was read. + uint packed = Unsafe.As(ref sBase) | 0xFF000000; + + Unsafe.As(ref dBase) = packed; sBase = ref Unsafe.Add(ref sBase, 3); dBase = ref Unsafe.Add(ref dBase, 4); @@ -73,10 +82,15 @@ internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) { - Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0); - Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1); - Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2); - Unsafe.Add(ref dBase, 3) = byte.MaxValue; + // The final triplet cannot use the widened read above, so assemble + // the same padded uint byte-by-byte before the overlapping store. + uint packed = + Unsafe.Add(ref sBase, 0u) | + ((uint)Unsafe.Add(ref sBase, 1u) << 8) | + ((uint)Unsafe.Add(ref sBase, 2u) << 16) | + 0xFF000000; + + Unsafe.As(ref dBase) = packed; sBase = ref Unsafe.Add(ref sBase, 3); dBase = ref Unsafe.Add(ref dBase, 4); diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs index 3c0973ad6..3907df58c 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs @@ -33,9 +33,19 @@ internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShu for (nuint i = 0; i < (uint)source.Length; i += 3) { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); + // The scalar remainder can run in-place after the vector body. Load + // the full 3-byte pixel into a register-sized value before stores so + // channel swaps cannot corrupt later reads from the same pixel. + uint packed = + Unsafe.Add(ref sBase, i + 0u) | + ((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | + ((uint)Unsafe.Add(ref sBase, i + 2u) << 16); + + ref byte pBase = ref Unsafe.As(ref packed); + + Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); + Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); + Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); } } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs index d5c6df2c8..68f34efd7 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs @@ -35,10 +35,16 @@ internal readonly struct DefaultShuffle4([ConstantExpected] byte control) : IShu for (nuint i = 0; i < (uint)source.Length; i += 4) { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); + // The generic path may be used with source and destination pointing + // at the same pixel. Load all channels first so subsequent stores + // index only staged bytes, matching the specialized uint shuffles. + uint packed = Unsafe.As(ref Unsafe.Add(ref sBase, i)); + ref byte pBase = ref Unsafe.As(ref packed); + + Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); + Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); + Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); + Unsafe.Add(ref dBase, i + 3u) = Unsafe.Add(ref pBase, p3); } } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index 3e7e44066..613406167 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -33,9 +33,15 @@ internal readonly struct DefaultShuffle4Slice3([ConstantExpected] byte control) for (nuint i = 0, j = 0; i < (uint)destination.Length; i += 3, j += 4) { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + j); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + j); + // Shrinking 4-byte pixels to 3 bytes can still be called in-place by + // tail code. Read the complete source pixel first, then write only + // the requested channels into the destination triplet. + uint packed = Unsafe.As(ref Unsafe.Add(ref sBase, j)); + ref byte pBase = ref Unsafe.As(ref packed); + + Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); + Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); + Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); } } } @@ -61,10 +67,18 @@ internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); - Unsafe.Add(ref dBase, 1) = Unsafe.As(ref Unsafe.Add(ref sBase, 1)); - Unsafe.Add(ref dBase, 2) = Unsafe.As(ref Unsafe.Add(ref sBase, 2)); - Unsafe.Add(ref dBase, 3) = Unsafe.As(ref Unsafe.Add(ref sBase, 3)); + // Stage the four source pixels before the 3-byte stores. Even + // though this path preserves XYZ order, the packed loads must happen + // before destination writes when the spans overlap. + uint packed0 = Unsafe.Add(ref sBase, 0u); + uint packed1 = Unsafe.Add(ref sBase, 1u); + uint packed2 = Unsafe.Add(ref sBase, 2u); + uint packed3 = Unsafe.Add(ref sBase, 3u); + + Unsafe.Add(ref dBase, 0u) = Unsafe.As(ref packed0); + Unsafe.Add(ref dBase, 1u) = Unsafe.As(ref packed1); + Unsafe.Add(ref dBase, 2u) = Unsafe.As(ref packed2); + Unsafe.Add(ref dBase, 3u) = Unsafe.As(ref packed3); sBase = ref Unsafe.Add(ref sBase, 4); dBase = ref Unsafe.Add(ref dBase, 4); @@ -72,7 +86,11 @@ internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); + // Same overlap rule as the unrolled loop: take the 4-byte source + // pixel before storing the 3-byte destination value. + uint packed = Unsafe.Add(ref sBase, 0u); + + Unsafe.Add(ref dBase, 0u) = Unsafe.As(ref packed); sBase = ref Unsafe.Add(ref sBase, 1); dBase = ref Unsafe.Add(ref dBase, 1); diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index dbeb54a80..8b2baec21 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -150,10 +151,15 @@ internal static partial class SimdUtils for (nuint i = 0; i < (uint)source.Length; i += 4) { - Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); + // Stage the scalar tail in a local Vector4 so p0..p3 index source + // values that were captured before any overlapping destination writes. + Vector4 v = Unsafe.As(ref Unsafe.Add(ref sBase, i)); + ref float pBase = ref Unsafe.As(ref v); + + Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); + Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); + Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); + Unsafe.Add(ref dBase, i + 3u) = Unsafe.Add(ref pBase, p3); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index dcd9ffd6c..84245254a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,6 +9,8 @@ using System.IO.Compression; using System.IO.Hashing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; @@ -900,7 +902,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore if (this.isCgbi) { - ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + this.ApplyCgbiTransform(scanSpan[1..], this.pngColorType); } this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); @@ -1035,7 +1037,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore if (this.isCgbi) { - ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + this.ApplyCgbiTransform(scanSpan[1..], this.pngColorType); } Span rowSpan = imageBuffer.DangerousGetRowSpan(currentRow); @@ -2505,39 +2507,289 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// /// The defiltered pixel bytes (without the leading filter byte). /// The PNG color type from IHDR. - private static void ApplyCgbiTransform(Span scanline, PngColorType colorType) + private void ApplyCgbiTransform(Span scanline, PngColorType colorType) { if (colorType == PngColorType.RgbWithAlpha) { Span pixels = MemoryMarshal.Cast(scanline); - for (int i = 0; i < pixels.Length; i++) + int i = 0; + + if (Vector512.IsHardwareAccelerated && pixels.Length >= 16) { - ref Rgba32 p = ref pixels[i]; - byte r = p.B; - byte g = p.G; - byte b = p.R; - byte a = p.A; + i = ApplyCgbiTransformVector512(scanline, pixels.Length); + } - if (a is not 0 and not byte.MaxValue) - { - // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) - int half = a >> 1; - r = (byte)Math.Min(byte.MaxValue, ((r * byte.MaxValue) + half) / a); - g = (byte)Math.Min(byte.MaxValue, ((g * byte.MaxValue) + half) / a); - b = (byte)Math.Min(byte.MaxValue, ((b * byte.MaxValue) + half) / a); - } + if (Vector256.IsHardwareAccelerated && Avx2.IsSupported && (pixels.Length - i) >= 8) + { + i = ApplyCgbiTransformVector256(scanline, i, pixels.Length); + } - p = new Rgba32(r, g, b, a); + if (Vector128.IsHardwareAccelerated && (pixels.Length - i) >= 4) + { + i = ApplyCgbiTransformVector128(scanline, i, pixels.Length); + } + + for (; i < pixels.Length; i++) + { + ref Rgba32 pixel = ref pixels[i]; + pixel = new Rgba32(pixel.B, pixel.G, pixel.R, pixel.A); + UndoCgbiPremultiplicationScalar(ref pixel); } } else if (colorType == PngColorType.Rgb) { - Span pixels = MemoryMarshal.Cast(scanline); - for (int i = 0; i < pixels.Length; i++) - { - ref Rgb24 p = ref pixels[i]; - (p.R, p.B) = (p.B, p.R); - } + // No alpha channel, so just swap R and B using built in SIMD-optimized pixel operations. + Span target = MemoryMarshal.Cast(scanline); + PixelOperations.Instance.FromBgr24Bytes(this.configuration, scanline, target, target.Length); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UndoCgbiPremultiplicationScalar(ref Rgba32 pixel) + { + byte a = pixel.A; + if (a is 0 or byte.MaxValue) + { + return; + } + + // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) + int half = a >> 1; + byte r = (byte)Math.Min(byte.MaxValue, ((pixel.R * byte.MaxValue) + half) / a); + byte g = (byte)Math.Min(byte.MaxValue, ((pixel.G * byte.MaxValue) + half) / a); + byte b = (byte)Math.Min(byte.MaxValue, ((pixel.B * byte.MaxValue) + half) / a); + pixel = new Rgba32(r, g, b, a); + } + + private static int ApplyCgbiTransformVector512(Span scanline, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = 0; + + Span temp = stackalloc byte[Vector512.Count]; + SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); + + // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting + // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. + // The generated mask only swaps bytes inside each pixel, so it remains + // correct for the optimized 512-bit byte shuffle helper. + Vector512 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); + + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + Vector512 byteMask = Vector512.Create(0xFF); + Vector512 opaque = Vector512.Create(0xFF); + Vector512 byteMax = Vector512.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 16; i += 16) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector512 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector512 rgba = Vector512_.ShuffleNative(bgra, shuffleMask); + Vector512 packed = rgba.AsInt32(); + Vector512 alpha = Vector512.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector512 partialMask = ~(Vector512.Equals(alpha, zero) | Vector512.Equals(alpha, opaque)); + + Vector512 r = packed & byteMask; + Vector512 g = Vector512.ShiftRightLogical(packed, 8) & byteMask; + Vector512 b = Vector512.ShiftRightLogical(packed, 16) & byteMask; + + Vector512 safeAlpha = Vector512.ConditionalSelect(partialMask, alpha, one); + Vector512 halfAlpha = Vector512.ShiftRightLogical(safeAlpha, 1); + Vector512 safeAlphaF = Vector512.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector512 unpremultipliedR = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector512 unpremultipliedG = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector512 unpremultipliedB = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector512 finalR = Vector512.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector512 finalG = Vector512.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector512 finalB = Vector512.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector512 result = + finalR | + Vector512.ShiftLeft(finalG, 8) | + Vector512.ShiftLeft(finalB, 16) | + Vector512.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } + + private static int ApplyCgbiTransformVector256(Span scanline, int startPixel, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = startPixel; + + Span temp = stackalloc byte[Vector512.Count]; + SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); + + // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting + // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. + // Avx2.Shuffle is 128-bit lane-local, and the generated mask repeats inside + // each lane, so no byte ever needs to cross the lane boundary. + Vector256 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); + + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + Vector256 byteMask = Vector256.Create(0xFF); + Vector256 opaque = Vector256.Create(0xFF); + Vector256 byteMax = Vector256.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 8; i += 8) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector256 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector256 rgba = Vector256_.ShufflePerLane(bgra, shuffleMask); + Vector256 packed = rgba.AsInt32(); + Vector256 alpha = Vector256.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector256 partialMask = ~(Vector256.Equals(alpha, zero) | Vector256.Equals(alpha, opaque)); + + Vector256 r = packed & byteMask; + Vector256 g = Vector256.ShiftRightLogical(packed, 8) & byteMask; + Vector256 b = Vector256.ShiftRightLogical(packed, 16) & byteMask; + + Vector256 safeAlpha = Vector256.ConditionalSelect(partialMask, alpha, one); + Vector256 halfAlpha = Vector256.ShiftRightLogical(safeAlpha, 1); + Vector256 safeAlphaF = Vector256.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector256 unpremultipliedR = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector256 unpremultipliedG = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector256 unpremultipliedB = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector256 finalR = Vector256.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector256 finalG = Vector256.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector256 finalB = Vector256.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector256 result = + finalR | + Vector256.ShiftLeft(finalG, 8) | + Vector256.ShiftLeft(finalB, 16) | + Vector256.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } + + private static int ApplyCgbiTransformVector128(Span scanline, int startPixel, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = startPixel; + + Span temp = stackalloc byte[Vector512.Count]; + SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); + + // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting + // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. + Vector128 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); + + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + Vector128 byteMask = Vector128.Create(0xFF); + Vector128 opaque = Vector128.Create(0xFF); + Vector128 byteMax = Vector128.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 4; i += 4) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector128 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector128 rgba = Vector128_.ShuffleNative(bgra, shuffleMask); + Vector128 packed = rgba.AsInt32(); + Vector128 alpha = Vector128.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector128 partialMask = ~(Vector128.Equals(alpha, zero) | Vector128.Equals(alpha, opaque)); + + Vector128 r = packed & byteMask; + Vector128 g = Vector128.ShiftRightLogical(packed, 8) & byteMask; + Vector128 b = Vector128.ShiftRightLogical(packed, 16) & byteMask; + + Vector128 safeAlpha = Vector128.ConditionalSelect(partialMask, alpha, one); + Vector128 halfAlpha = Vector128.ShiftRightLogical(safeAlpha, 1); + Vector128 safeAlphaF = Vector128.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector128 unpremultipliedR = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector128 unpremultipliedG = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector128 unpremultipliedB = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector128 finalR = Vector128.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector128 finalG = Vector128.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector128 finalB = Vector128.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector128 result = + finalR | + Vector128.ShiftLeft(finalG, 8) | + Vector128.ShiftLeft(finalB, 16) | + Vector128.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index d3a699c49..2e452b896 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -722,10 +722,32 @@ public partial class PngDecoderTests [WithFile(TestImages.Png.Cgbi.Flecks, PixelTypes.Rgb24)] public void Decode_AppleCgBI(TestImageProvider provider) where TPixel : unmanaged, IPixel + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunDecodeAppleCgbi, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic, + provider, + provider.PixelType.ToString()); + + private static void RunDecodeAppleCgbi(string providerDump, string pixelType) { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider, ImageComparer.Exact); + if (Enum.Parse(pixelType) == PixelTypes.Rgb24) + { + TestImageProvider provider = + FeatureTestRunner.DeserializeForXunit>(providerDump); + + using Image image = provider.GetImage(PngDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider, ImageComparer.Exact); + + return; + } + + TestImageProvider rgbaProvider = + FeatureTestRunner.DeserializeForXunit>(providerDump); + + using Image rgbaImage = rgbaProvider.GetImage(PngDecoder.Instance); + rgbaImage.DebugSave(rgbaProvider); + rgbaImage.CompareToReferenceOutput(rgbaProvider, ImageComparer.Exact); } [Theory] From 29ae5f8409932728c4d5bff1369488e8b564cb17 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 10:28:30 +0200 Subject: [PATCH 187/240] Guard CgBI bit depth and color type --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 16 ++++++++++++++ .../Formats/Png/PngDecoderTests.cs | 22 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 4 ++++ .../Input/Png/cgbi/colors-cgbi-bitdepth16.png | 3 +++ .../Input/Png/cgbi/colors-cgbi-palette.png | 3 +++ 5 files changed, 48 insertions(+) create mode 100644 tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png create mode 100644 tests/Images/Input/Png/cgbi/colors-cgbi-palette.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 84245254a..0d93429bb 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1431,6 +1431,22 @@ internal sealed class PngDecoderCore : ImageDecoderCore this.pngColorType = this.header.ColorType; this.Dimensions = new Size(this.header.Width, this.header.Height); + + // Apple's pngcrush emits the CgBI chunk before IHDR, so the header + // compatibility check is deferred until both chunks have been seen. + if (this.isCgbi) + { + ThrowIfInvalidCgbiContent(this.header); + } + } + + private static void ThrowIfInvalidCgbiContent(in PngHeader header) + { + if (header.BitDepth != 8 || (header.ColorType is not PngColorType.Rgb and not PngColorType.RgbWithAlpha)) + { + PngThrowHelper.ThrowInvalidImageContentException( + $"CgBI is only supported for 8-bit truecolor images. Was bit depth '{header.BitDepth}', color type '{header.ColorType}'."); + } } /// diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 2e452b896..2fbbe695e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -767,6 +767,28 @@ public partial class PngDecoderTests Assert.Equal(expectedHeight, imageInfo.Height); } + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Identify_CgBI_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + InvalidImageContentException ex = Assert.Throws(() => Image.Identify(stream)); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + [Theory] + [WithFile(TestImages.Png.Cgbi.BitDepth16, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Palette, PixelTypes.Rgba32)] + public void Decode_CgBI_IncompatibleHeader_ThrowsInvalidImageContentException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + InvalidImageContentException ex = Assert.Throws( + () => { using Image image = provider.GetImage(PngDecoder.Instance); }); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + [Theory] [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1b6ae5685..7b43ab262 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -189,6 +189,10 @@ public static class TestImages // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 public const string Issue410 = "Png/issues/Issue_410.png"; + + // Synthetic fixtures derived from colors.png to exercise CgBI validation. + public const string BitDepth16 = "Png/cgbi/colors-cgbi-bitdepth16.png"; + public const string Palette = "Png/cgbi/colors-cgbi-palette.png"; } public static class Bad diff --git a/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png b/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png new file mode 100644 index 000000000..18cfa9246 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59610bc03f6ca867e5f71c574b3a0d1942c9e3a230c8a32bf3007cb82f286866 +size 12853 diff --git a/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png b/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png new file mode 100644 index 000000000..f6406559b --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a2f20c69ae423523a8f41887e3f37257a338f2220c2ea44d35c87daf8c3aa3 +size 12853 From fdc15d283ec21dac7df993d4795389a4bab74255 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 10:58:59 +0200 Subject: [PATCH 188/240] Separate Zlib header validation fom stream reading --- .../Compression/Zlib/ChunkedReadStream.cs | 125 ++++++++++ .../Compression/Zlib/ZlibInflateStream.cs | 219 ++---------------- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 26 ++- 3 files changed, 167 insertions(+), 203 deletions(-) create mode 100644 src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs diff --git a/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs b/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs new file mode 100644 index 000000000..8c5189d4a --- /dev/null +++ b/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs @@ -0,0 +1,125 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// A read-only stream over a sequence of length-delimited segments. Bytes are +/// pulled from the inner stream up to the current segment's remaining length; +/// when the segment is exhausted the supplied delegate is invoked to advance +/// to the next segment and return its length. The inner stream is not owned +/// and is not disposed. +/// +internal sealed class ChunkedReadStream : Stream +{ + private static readonly Func GetDataNoOp = () => 0; + + private readonly BufferedReadStream innerStream; + private readonly Func getData; + private int currentDataRemaining; + + public ChunkedReadStream(BufferedReadStream innerStream) + : this(innerStream, GetDataNoOp) + { + } + + public ChunkedReadStream(BufferedReadStream innerStream, Func getData) + { + this.innerStream = innerStream; + this.getData = getData; + } + + /// + public override bool CanRead => this.innerStream.CanRead; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => throw new NotSupportedException(); + + /// + public override long Length => throw new NotSupportedException(); + + /// + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + /// + /// Sets the number of bytes available to read from the current segment. + /// Must be called before reading each segment. + /// + public void SetCurrentSegmentLength(int bytes) => this.currentDataRemaining = bytes; + + /// + public override void Flush() => throw new NotSupportedException(); + + /// + public override int ReadByte() + { + this.currentDataRemaining--; + return this.innerStream.ReadByte(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (this.currentDataRemaining is 0) + { + // Current segment is exhausted; ask the caller for the next one. + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining is 0) + { + return 0; + } + } + + int bytesToRead = Math.Min(count, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + long innerStreamLength = this.innerStream.Length; + + // Keep reading data until we've reached the end of the stream or filled the buffer. + int bytesRead = 0; + offset += totalBytesRead; + while (this.currentDataRemaining is 0 && totalBytesRead < count) + { + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining is 0) + { + return totalBytesRead; + } + + offset += bytesRead; + + if (offset >= innerStreamLength || offset >= count) + { + return totalBytesRead; + } + + bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + if (bytesRead == 0) + { + return totalBytesRead; + } + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); +} diff --git a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs index 513171b17..11f34dac8 100644 --- a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs @@ -8,9 +8,11 @@ using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Compression.Zlib; /// -/// Provides methods and properties for deframing streams from PNGs. +/// Reads chunked input, parses the zlib CMF/FLG header, and exposes a +/// over the remaining DEFLATE payload. The +/// Adler-32 trailer is not validated. /// -internal sealed class ZlibInflateStream : Stream +internal sealed class ZlibInflateStream : IDisposable { /// /// Used to read the Adler-32 and Crc-32 checksums. @@ -19,94 +21,13 @@ internal sealed class ZlibInflateStream : Stream /// private static readonly byte[] ChecksumBuffer = new byte[4]; - /// - /// A default delegate to get more data from the inner stream. - /// - private static readonly Func GetDataNoOp = () => 0; - - /// - /// The inner raw memory stream. - /// - private readonly BufferedReadStream innerStream; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The current data remaining to be read. - /// - private int currentDataRemaining; - - /// - /// Delegate to get more data once we've exhausted the current data remaining. - /// - private readonly Func getData; - - /// - /// When true, the inflated payload is treated as a raw DEFLATE stream with no zlib - /// CMF/FLG header (and no Adler-32 trailer). This is required to decode IDATs in - /// Apple's proprietary CgBI PNG variant. - /// - private readonly bool noHeader; + private readonly ChunkedReadStream segmentStream; - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. public ZlibInflateStream(BufferedReadStream innerStream) - : this(innerStream, GetDataNoOp, noHeader: false) - { - } + => this.segmentStream = new ChunkedReadStream(innerStream); - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - /// A delegate to get more data from the inner stream. public ZlibInflateStream(BufferedReadStream innerStream, Func getData) - : this(innerStream, getData, noHeader: false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - /// A delegate to get more data from the inner stream. - /// - /// When , the payload is treated as raw DEFLATE with no zlib header. - /// - public ZlibInflateStream(BufferedReadStream innerStream, Func getData, bool noHeader) - { - this.innerStream = innerStream; - this.getData = getData; - this.noHeader = noHeader; - } - - /// - public override bool CanRead => this.innerStream.CanRead; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => throw new NotSupportedException(); - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + => this.segmentStream = new ChunkedReadStream(innerStream, getData); /// /// Gets the compressed stream over the deframed inner stream. @@ -114,15 +35,16 @@ internal sealed class ZlibInflateStream : Stream public DeflateStream? CompressedStream { get; private set; } /// - /// Adds new bytes from a frame found in the original stream. + /// Sets the length of the next segment of compressed input and, on first + /// call, parses the zlib header. /// - /// The current remaining data according to the chunk length. - /// Whether the chunk to be inflated is a critical chunk. + /// The remaining data length for the current segment. + /// Whether to throw on a malformed zlib header. /// The . [MemberNotNullWhen(true, nameof(CompressedStream))] public bool AllocateNewBytes(int bytes, bool isCriticalChunk) { - this.currentDataRemaining = bytes; + this.segmentStream.SetCurrentSegmentLength(bytes); if (this.CompressedStream is null) { return this.InitializeInflateStream(isCriticalChunk); @@ -131,114 +53,15 @@ internal sealed class ZlibInflateStream : Stream return true; } - /// - public override void Flush() => throw new NotSupportedException(); - - /// - public override int ReadByte() + public void Dispose() { - this.currentDataRemaining--; - return this.innerStream.ReadByte(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (this.currentDataRemaining is 0) - { - // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks. - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining is 0) - { - return 0; - } - } - - int bytesToRead = Math.Min(count, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - long innerStreamLength = this.innerStream.Length; - - // Keep reading data until we've reached the end of the stream or filled the buffer. - int bytesRead = 0; - offset += totalBytesRead; - while (this.currentDataRemaining is 0 && totalBytesRead < count) - { - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining is 0) - { - return totalBytesRead; - } - - offset += bytesRead; - - if (offset >= innerStreamLength || offset >= count) - { - return totalBytesRead; - } - - bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - if (bytesRead == 0) - { - return totalBytesRead; - } - - totalBytesRead += bytesRead; - } - - return totalBytesRead; - } - - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - /// - public override void SetLength(long value) => throw new NotSupportedException(); - - /// - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // Dispose managed resources. - if (this.CompressedStream != null) - { - this.CompressedStream.Dispose(); - this.CompressedStream = null; - } - } - - base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; + this.CompressedStream?.Dispose(); + this.segmentStream?.Dispose(); } [MemberNotNullWhen(true, nameof(CompressedStream))] private bool InitializeInflateStream(bool isCriticalChunk) { - // Apple CgBI IDATs omit the zlib CMF/FLG header and the Adler-32 trailer, - // wrapping a raw DEFLATE payload directly. Skip the header parsing in that mode. - if (this.noHeader) - { - this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); - return true; - } - // Read the zlib header : http://tools.ietf.org/html/rfc1950 // CMF(Compression Method and flags) // This byte is divided into a 4 - bit compression method and a @@ -250,9 +73,8 @@ internal sealed class ZlibInflateStream : Stream // +---+---+ // |CMF|FLG| // +---+---+ - int cmf = this.innerStream.ReadByte(); - int flag = this.innerStream.ReadByte(); - this.currentDataRemaining -= 2; + int cmf = this.segmentStream.ReadByte(); + int flag = this.segmentStream.ReadByte(); if (cmf == -1 || flag == -1) { return false; @@ -290,16 +112,13 @@ internal sealed class ZlibInflateStream : Stream { // We don't need this for inflate so simply skip by the next four bytes. // https://tools.ietf.org/html/rfc1950#page-6 - if (this.innerStream.Read(ChecksumBuffer, 0, 4) != 4) + if (this.segmentStream.Read(ChecksumBuffer, 0, 4) != 4) { return false; } - - this.currentDataRemaining -= 4; } - // Initialize the deflate BufferedReadStream. - this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + this.CompressedStream = new DeflateStream(this.segmentStream, CompressionMode.Decompress, leaveOpen: true); return true; } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 0d93429bb..49b6fabc6 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -767,7 +767,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// The length of the chunk that containing the compressed scanline data. /// The pixel data. /// The png metadata - /// A delegate to get more data from the inner stream for . + /// A delegate to get more data from the inner stream when chunk boundaries are crossed. /// The frame control /// The cancellation token. private void ReadScanlines( @@ -779,14 +779,34 @@ internal sealed class PngDecoderCore : ImageDecoderCore CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using ZlibInflateStream inflateStream = new(this.currentStream, getData, noHeader: this.isCgbi); + // CgBI IDATs wrap a raw DEFLATE payload directly (no zlib CMF/FLG header + // and no Adler-32 trailer); skip the zlib header parser entirely. + if (this.isCgbi) + { + using ChunkedReadStream segmentStream = new(this.currentStream, getData); + segmentStream.SetCurrentSegmentLength(chunkLength); + using DeflateStream cgbiDataStream = new(segmentStream, CompressionMode.Decompress, leaveOpen: true); + this.DecodeFromDeflate(cgbiDataStream, image, pngMetadata, frameControl, cancellationToken); + return; + } + + using ZlibInflateStream inflateStream = new(this.currentStream, getData); if (!inflateStream.AllocateNewBytes(chunkLength, !this.hasImageData)) { return; } - DeflateStream dataStream = inflateStream.CompressedStream!; + this.DecodeFromDeflate(inflateStream.CompressedStream!, image, pngMetadata, frameControl, cancellationToken); + } + private void DecodeFromDeflate( + DeflateStream dataStream, + ImageFrame image, + PngMetadata pngMetadata, + in FrameControl frameControl, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) { this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); From a68921cac36dc6f5590ce2921fb0e27b6e033954 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 12:50:51 +0200 Subject: [PATCH 189/240] Break out cgbi helper methods --- .../Formats/Png/PngCgbiProcessor.cs | 325 ++++++++++++++++++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 305 +--------------- .../Formats/Png/PngCgbiProcessorTests.cs | 174 ++++++++++ 3 files changed, 501 insertions(+), 303 deletions(-) create mode 100644 src/ImageSharp/Formats/Png/PngCgbiProcessor.cs create mode 100644 tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs new file mode 100644 index 000000000..6478acced --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -0,0 +1,325 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.SimdUtils; + +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Reverses the pixel mangling applied by Apple's CgBI PNG variant. CgBI files +/// (emitted by pngcrush -iphone) swap channel order from RGB(A) to BGR(A) +/// and premultiply RGB samples by alpha. This converts a defiltered scanline back +/// to standard PNG semantics in place so the existing scanline processors can +/// consume it unchanged. CgBI is only emitted for 8-bit truecolor (with or +/// without alpha); other color types are left alone. +/// +/// +/// See https://theapplewiki.com/wiki/PNG_CgBI_Format +/// +internal static class PngCgbiProcessor +{ + // Per-pixel byte indices that swap CgBI's BGRA layout to Rgba32's RGBA. + // MMShuffle3012 expands to [2, 1, 0, 3] per 4-byte pixel; the same 64-byte + // sequence seeds all three shuffle masks (V128/V256 ctors take the leading + // 16/32 bytes). + private static readonly Vector128 BgraToRgbaShuffle128 = Vector128.Create(BuildShuffleBytes()); + + private static readonly Vector256 BgraToRgbaShuffle256 = Vector256.Create(BuildShuffleBytes()); + + private static readonly Vector512 BgraToRgbaShuffle512 = Vector512.Create(BuildShuffleBytes()); + + /// + /// Applies the inverse of Apple's CgBI pixel mangling to a defiltered scanline in place. + /// + /// The configuration used by the Rgb24 R/B swap. + /// The defiltered pixel bytes (without the leading filter byte). + /// The PNG color type from IHDR. + public static void ApplyTransform(Configuration configuration, Span scanline, PngColorType colorType) + { + if (colorType == PngColorType.RgbWithAlpha) + { + Span pixels = MemoryMarshal.Cast(scanline); + int i = 0; + + // Avx512BW is required for the per-byte vpshufb. Without it, ShuffleNative + // falls back to a software cross-lane shuffle that is slower than the V256 + // path, so skip V512 entirely on Avx512F-only hosts. + if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported && pixels.Length >= 16) + { + i = ApplyTransformVector512(scanline, pixels.Length); + } + + if (Vector256.IsHardwareAccelerated && Avx2.IsSupported && (pixels.Length - i) >= 8) + { + i = ApplyTransformVector256(scanline, i, pixels.Length); + } + + if (Vector128.IsHardwareAccelerated && (pixels.Length - i) >= 4) + { + i = ApplyTransformVector128(scanline, i, pixels.Length); + } + + for (; i < pixels.Length; i++) + { + ref Rgba32 pixel = ref pixels[i]; + pixel = new Rgba32(pixel.B, pixel.G, pixel.R, pixel.A); + UndoPremultiplicationScalar(ref pixel); + } + } + else if (colorType == PngColorType.Rgb) + { + // No alpha channel, so just swap R and B using built in SIMD-optimized pixel operations. + Span target = MemoryMarshal.Cast(scanline); + PixelOperations.Instance.FromBgr24Bytes(configuration, scanline, target, target.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UndoPremultiplicationScalar(ref Rgba32 pixel) + { + byte a = pixel.A; + if (a is 0 or byte.MaxValue) + { + return; + } + + // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) + int half = a >> 1; + byte r = (byte)Math.Min(byte.MaxValue, ((pixel.R * byte.MaxValue) + half) / a); + byte g = (byte)Math.Min(byte.MaxValue, ((pixel.G * byte.MaxValue) + half) / a); + byte b = (byte)Math.Min(byte.MaxValue, ((pixel.B * byte.MaxValue) + half) / a); + pixel = new Rgba32(r, g, b, a); + } + + internal static int ApplyTransformVector512(Span scanline, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = 0; + + // The mask only swaps bytes inside each 4-byte pixel, so it is correct + // for the per-lane Avx512BW.Shuffle that ShuffleNative selects here. + Vector512 shuffleMask = BgraToRgbaShuffle512; + + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + Vector512 byteMask = Vector512.Create(0xFF); + Vector512 opaque = Vector512.Create(0xFF); + Vector512 byteMax = Vector512.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 16; i += 16) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector512 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector512 rgba = Vector512_.ShuffleNative(bgra, shuffleMask); + Vector512 packed = rgba.AsInt32(); + Vector512 alpha = Vector512.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector512 partialMask = ~(Vector512.Equals(alpha, zero) | Vector512.Equals(alpha, opaque)); + + Vector512 r = packed & byteMask; + Vector512 g = Vector512.ShiftRightLogical(packed, 8) & byteMask; + Vector512 b = Vector512.ShiftRightLogical(packed, 16) & byteMask; + + Vector512 safeAlpha = Vector512.ConditionalSelect(partialMask, alpha, one); + Vector512 halfAlpha = Vector512.ShiftRightLogical(safeAlpha, 1); + Vector512 safeAlphaF = Vector512.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector512 unpremultipliedR = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector512 unpremultipliedG = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector512 unpremultipliedB = Vector512.Min( + byteMax, + Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector512 finalR = Vector512.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector512 finalG = Vector512.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector512 finalB = Vector512.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector512 result = + finalR | + Vector512.ShiftLeft(finalG, 8) | + Vector512.ShiftLeft(finalB, 16) | + Vector512.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } + + internal static int ApplyTransformVector256(Span scanline, int startPixel, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = startPixel; + + // vpshufb is 128-bit lane-local and uses only the low 4 bits of each + // index, so the same per-pixel [2,1,0,3] pattern in both lanes keeps + // every byte inside its own lane. + Vector256 shuffleMask = BgraToRgbaShuffle256; + + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + Vector256 byteMask = Vector256.Create(0xFF); + Vector256 opaque = Vector256.Create(0xFF); + Vector256 byteMax = Vector256.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 8; i += 8) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector256 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector256 rgba = Vector256_.ShufflePerLane(bgra, shuffleMask); + Vector256 packed = rgba.AsInt32(); + Vector256 alpha = Vector256.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector256 partialMask = ~(Vector256.Equals(alpha, zero) | Vector256.Equals(alpha, opaque)); + + Vector256 r = packed & byteMask; + Vector256 g = Vector256.ShiftRightLogical(packed, 8) & byteMask; + Vector256 b = Vector256.ShiftRightLogical(packed, 16) & byteMask; + + Vector256 safeAlpha = Vector256.ConditionalSelect(partialMask, alpha, one); + Vector256 halfAlpha = Vector256.ShiftRightLogical(safeAlpha, 1); + Vector256 safeAlphaF = Vector256.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector256 unpremultipliedR = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector256 unpremultipliedG = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector256 unpremultipliedB = Vector256.Min( + byteMax, + Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector256 finalR = Vector256.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector256 finalG = Vector256.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector256 finalB = Vector256.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector256 result = + finalR | + Vector256.ShiftLeft(finalG, 8) | + Vector256.ShiftLeft(finalB, 16) | + Vector256.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } + + internal static int ApplyTransformVector128(Span scanline, int startPixel, int pixelCount) + { + ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); + int i = startPixel; + + Vector128 shuffleMask = BgraToRgbaShuffle128; + + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + Vector128 byteMask = Vector128.Create(0xFF); + Vector128 opaque = Vector128.Create(0xFF); + Vector128 byteMax = Vector128.Create((int)byte.MaxValue); + + for (; i <= pixelCount - 4; i += 4) + { + ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); + Vector128 bgra = Unsafe.ReadUnaligned>(ref blockRef); + Vector128 rgba = Vector128_.ShuffleNative(bgra, shuffleMask); + Vector128 packed = rgba.AsInt32(); + Vector128 alpha = Vector128.ShiftRightLogical(packed, 24); + + // Fully transparent and fully opaque pixels are identity cases for + // unpremultiplication. Masking them keeps the scalar behavior and lets + // safeAlpha avoid dividing by zero for alpha == 0. + Vector128 partialMask = ~(Vector128.Equals(alpha, zero) | Vector128.Equals(alpha, opaque)); + + Vector128 r = packed & byteMask; + Vector128 g = Vector128.ShiftRightLogical(packed, 8) & byteMask; + Vector128 b = Vector128.ShiftRightLogical(packed, 16) & byteMask; + + Vector128 safeAlpha = Vector128.ConditionalSelect(partialMask, alpha, one); + Vector128 halfAlpha = Vector128.ShiftRightLogical(safeAlpha, 1); + Vector128 safeAlphaF = Vector128.ConvertToSingle(safeAlpha); + + // The scalar path computes ((c * 255) + (a >> 1)) / a with integer + // division. Floor the positive quotient before converting so SIMD does + // not use the default round-to-nearest conversion and drift by one. + Vector128 unpremultipliedR = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + + Vector128 unpremultipliedG = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + + Vector128 unpremultipliedB = Vector128.Min( + byteMax, + Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + + // ConditionalSelect applies the expensive unpremultiply only to pixels + // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the + // shuffled channel values exactly as the scalar path does. + Vector128 finalR = Vector128.ConditionalSelect(partialMask, unpremultipliedR, r); + Vector128 finalG = Vector128.ConditionalSelect(partialMask, unpremultipliedG, g); + Vector128 finalB = Vector128.ConditionalSelect(partialMask, unpremultipliedB, b); + + // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so + // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 + // recreates the in-memory RGBA bytes for the unaligned store. + Vector128 result = + finalR | + Vector128.ShiftLeft(finalG, 8) | + Vector128.ShiftLeft(finalB, 16) | + Vector128.ShiftLeft(alpha, 24); + + Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); + } + + return i; + } + + private static byte[] BuildShuffleBytes() + { + byte[] bytes = new byte[64]; + Span span = bytes; + Shuffle.MMShuffleSpan(ref span, Shuffle.MMShuffle3012); + return bytes; + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 49b6fabc6..f3e2bbdbe 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,8 +9,6 @@ using System.IO.Compression; using System.IO.Hashing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; using System.Text; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; @@ -922,7 +920,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore if (this.isCgbi) { - this.ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + PngCgbiProcessor.ApplyTransform(this.configuration, scanSpan[1..], this.pngColorType); } this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer); @@ -1057,7 +1055,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore if (this.isCgbi) { - this.ApplyCgbiTransform(scanSpan[1..], this.pngColorType); + PngCgbiProcessor.ApplyTransform(this.configuration, scanSpan[1..], this.pngColorType); } Span rowSpan = imageBuffer.DangerousGetRowSpan(currentRow); @@ -2529,303 +2527,4 @@ internal sealed class PngDecoderCore : ImageDecoderCore private void SwapScanlineBuffers() => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); - - /// - /// Applies the inverse of Apple's CgBI pixel mangling to a defiltered scanline. - /// CgBI PNGs are emitted by pngcrush -iphone with channel order swapped - /// from RGB(A) to BGR(A) and RGB samples premultiplied by alpha. This converts - /// the bytes back to standard PNG semantics in place so the existing scanline - /// processors can consume them unchanged. CgBI is only emitted for 8-bit - /// truecolor (with or without alpha); other color types are left alone. - /// - /// - /// See https://theapplewiki.com/wiki/PNG_CgBI_Format - /// - /// The defiltered pixel bytes (without the leading filter byte). - /// The PNG color type from IHDR. - private void ApplyCgbiTransform(Span scanline, PngColorType colorType) - { - if (colorType == PngColorType.RgbWithAlpha) - { - Span pixels = MemoryMarshal.Cast(scanline); - int i = 0; - - if (Vector512.IsHardwareAccelerated && pixels.Length >= 16) - { - i = ApplyCgbiTransformVector512(scanline, pixels.Length); - } - - if (Vector256.IsHardwareAccelerated && Avx2.IsSupported && (pixels.Length - i) >= 8) - { - i = ApplyCgbiTransformVector256(scanline, i, pixels.Length); - } - - if (Vector128.IsHardwareAccelerated && (pixels.Length - i) >= 4) - { - i = ApplyCgbiTransformVector128(scanline, i, pixels.Length); - } - - for (; i < pixels.Length; i++) - { - ref Rgba32 pixel = ref pixels[i]; - pixel = new Rgba32(pixel.B, pixel.G, pixel.R, pixel.A); - UndoCgbiPremultiplicationScalar(ref pixel); - } - } - else if (colorType == PngColorType.Rgb) - { - // No alpha channel, so just swap R and B using built in SIMD-optimized pixel operations. - Span target = MemoryMarshal.Cast(scanline); - PixelOperations.Instance.FromBgr24Bytes(this.configuration, scanline, target, target.Length); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void UndoCgbiPremultiplicationScalar(ref Rgba32 pixel) - { - byte a = pixel.A; - if (a is 0 or byte.MaxValue) - { - return; - } - - // Reverse: c' = c * a / 255 => c = round(c' * 255 / a) - int half = a >> 1; - byte r = (byte)Math.Min(byte.MaxValue, ((pixel.R * byte.MaxValue) + half) / a); - byte g = (byte)Math.Min(byte.MaxValue, ((pixel.G * byte.MaxValue) + half) / a); - byte b = (byte)Math.Min(byte.MaxValue, ((pixel.B * byte.MaxValue) + half) / a); - pixel = new Rgba32(r, g, b, a); - } - - private static int ApplyCgbiTransformVector512(Span scanline, int pixelCount) - { - ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); - int i = 0; - - Span temp = stackalloc byte[Vector512.Count]; - SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); - - // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting - // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. - // The generated mask only swaps bytes inside each pixel, so it remains - // correct for the optimized 512-bit byte shuffle helper. - Vector512 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - Vector512 zero = Vector512.Zero; - Vector512 one = Vector512.One; - Vector512 byteMask = Vector512.Create(0xFF); - Vector512 opaque = Vector512.Create(0xFF); - Vector512 byteMax = Vector512.Create((int)byte.MaxValue); - - for (; i <= pixelCount - 16; i += 16) - { - ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); - Vector512 bgra = Unsafe.ReadUnaligned>(ref blockRef); - Vector512 rgba = Vector512_.ShuffleNative(bgra, shuffleMask); - Vector512 packed = rgba.AsInt32(); - Vector512 alpha = Vector512.ShiftRightLogical(packed, 24); - - // Fully transparent and fully opaque pixels are identity cases for - // unpremultiplication. Masking them keeps the scalar behavior and lets - // safeAlpha avoid dividing by zero for alpha == 0. - Vector512 partialMask = ~(Vector512.Equals(alpha, zero) | Vector512.Equals(alpha, opaque)); - - Vector512 r = packed & byteMask; - Vector512 g = Vector512.ShiftRightLogical(packed, 8) & byteMask; - Vector512 b = Vector512.ShiftRightLogical(packed, 16) & byteMask; - - Vector512 safeAlpha = Vector512.ConditionalSelect(partialMask, alpha, one); - Vector512 halfAlpha = Vector512.ShiftRightLogical(safeAlpha, 1); - Vector512 safeAlphaF = Vector512.ConvertToSingle(safeAlpha); - - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. - Vector512 unpremultipliedR = Vector512.Min( - byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); - - Vector512 unpremultipliedG = Vector512.Min( - byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); - - Vector512 unpremultipliedB = Vector512.Min( - byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); - - // ConditionalSelect applies the expensive unpremultiply only to pixels - // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the - // shuffled channel values exactly as the scalar path does. - Vector512 finalR = Vector512.ConditionalSelect(partialMask, unpremultipliedR, r); - Vector512 finalG = Vector512.ConditionalSelect(partialMask, unpremultipliedG, g); - Vector512 finalB = Vector512.ConditionalSelect(partialMask, unpremultipliedB, b); - - // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so - // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 - // recreates the in-memory RGBA bytes for the unaligned store. - Vector512 result = - finalR | - Vector512.ShiftLeft(finalG, 8) | - Vector512.ShiftLeft(finalB, 16) | - Vector512.ShiftLeft(alpha, 24); - - Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); - } - - return i; - } - - private static int ApplyCgbiTransformVector256(Span scanline, int startPixel, int pixelCount) - { - ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); - int i = startPixel; - - Span temp = stackalloc byte[Vector512.Count]; - SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); - - // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting - // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. - // Avx2.Shuffle is 128-bit lane-local, and the generated mask repeats inside - // each lane, so no byte ever needs to cross the lane boundary. - Vector256 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - Vector256 zero = Vector256.Zero; - Vector256 one = Vector256.One; - Vector256 byteMask = Vector256.Create(0xFF); - Vector256 opaque = Vector256.Create(0xFF); - Vector256 byteMax = Vector256.Create((int)byte.MaxValue); - - for (; i <= pixelCount - 8; i += 8) - { - ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); - Vector256 bgra = Unsafe.ReadUnaligned>(ref blockRef); - Vector256 rgba = Vector256_.ShufflePerLane(bgra, shuffleMask); - Vector256 packed = rgba.AsInt32(); - Vector256 alpha = Vector256.ShiftRightLogical(packed, 24); - - // Fully transparent and fully opaque pixels are identity cases for - // unpremultiplication. Masking them keeps the scalar behavior and lets - // safeAlpha avoid dividing by zero for alpha == 0. - Vector256 partialMask = ~(Vector256.Equals(alpha, zero) | Vector256.Equals(alpha, opaque)); - - Vector256 r = packed & byteMask; - Vector256 g = Vector256.ShiftRightLogical(packed, 8) & byteMask; - Vector256 b = Vector256.ShiftRightLogical(packed, 16) & byteMask; - - Vector256 safeAlpha = Vector256.ConditionalSelect(partialMask, alpha, one); - Vector256 halfAlpha = Vector256.ShiftRightLogical(safeAlpha, 1); - Vector256 safeAlphaF = Vector256.ConvertToSingle(safeAlpha); - - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. - Vector256 unpremultipliedR = Vector256.Min( - byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); - - Vector256 unpremultipliedG = Vector256.Min( - byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); - - Vector256 unpremultipliedB = Vector256.Min( - byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); - - // ConditionalSelect applies the expensive unpremultiply only to pixels - // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the - // shuffled channel values exactly as the scalar path does. - Vector256 finalR = Vector256.ConditionalSelect(partialMask, unpremultipliedR, r); - Vector256 finalG = Vector256.ConditionalSelect(partialMask, unpremultipliedG, g); - Vector256 finalB = Vector256.ConditionalSelect(partialMask, unpremultipliedB, b); - - // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so - // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 - // recreates the in-memory RGBA bytes for the unaligned store. - Vector256 result = - finalR | - Vector256.ShiftLeft(finalG, 8) | - Vector256.ShiftLeft(finalB, 16) | - Vector256.ShiftLeft(alpha, 24); - - Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); - } - - return i; - } - - private static int ApplyCgbiTransformVector128(Span scanline, int startPixel, int pixelCount) - { - ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); - int i = startPixel; - - Span temp = stackalloc byte[Vector512.Count]; - SimdUtils.Shuffle.MMShuffleSpan(ref temp, SimdUtils.Shuffle.MMShuffle3012); - - // MMShuffle3012 expands to [2, 1, 0, 3] for each 4-byte pixel, converting - // CgBI's BGRA byte order to Rgba32's RGBA layout while keeping alpha in place. - Vector128 shuffleMask = Unsafe.As>(ref MemoryMarshal.GetReference(temp)); - - Vector128 zero = Vector128.Zero; - Vector128 one = Vector128.One; - Vector128 byteMask = Vector128.Create(0xFF); - Vector128 opaque = Vector128.Create(0xFF); - Vector128 byteMax = Vector128.Create((int)byte.MaxValue); - - for (; i <= pixelCount - 4; i += 4) - { - ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); - Vector128 bgra = Unsafe.ReadUnaligned>(ref blockRef); - Vector128 rgba = Vector128_.ShuffleNative(bgra, shuffleMask); - Vector128 packed = rgba.AsInt32(); - Vector128 alpha = Vector128.ShiftRightLogical(packed, 24); - - // Fully transparent and fully opaque pixels are identity cases for - // unpremultiplication. Masking them keeps the scalar behavior and lets - // safeAlpha avoid dividing by zero for alpha == 0. - Vector128 partialMask = ~(Vector128.Equals(alpha, zero) | Vector128.Equals(alpha, opaque)); - - Vector128 r = packed & byteMask; - Vector128 g = Vector128.ShiftRightLogical(packed, 8) & byteMask; - Vector128 b = Vector128.ShiftRightLogical(packed, 16) & byteMask; - - Vector128 safeAlpha = Vector128.ConditionalSelect(partialMask, alpha, one); - Vector128 halfAlpha = Vector128.ShiftRightLogical(safeAlpha, 1); - Vector128 safeAlphaF = Vector128.ConvertToSingle(safeAlpha); - - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. - Vector128 unpremultipliedR = Vector128.Min( - byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); - - Vector128 unpremultipliedG = Vector128.Min( - byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); - - Vector128 unpremultipliedB = Vector128.Min( - byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); - - // ConditionalSelect applies the expensive unpremultiply only to pixels - // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the - // shuffled channel values exactly as the scalar path does. - Vector128 finalR = Vector128.ConditionalSelect(partialMask, unpremultipliedR, r); - Vector128 finalG = Vector128.ConditionalSelect(partialMask, unpremultipliedG, g); - Vector128 finalB = Vector128.ConditionalSelect(partialMask, unpremultipliedB, b); - - // Rgba32 is laid out as little-endian 0xAABBGGRR in an int lane, so - // shifting the unpacked channels back to byte offsets 0, 1, 2, and 3 - // recreates the in-memory RGBA bytes for the unaligned store. - Vector128 result = - finalR | - Vector128.ShiftLeft(finalG, 8) | - Vector128.ShiftLeft(finalB, 16) | - Vector128.ShiftLeft(alpha, 24); - - Unsafe.WriteUnaligned(ref blockRef, result.AsByte()); - } - - return i; - } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs new file mode 100644 index 000000000..426afb6d4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs @@ -0,0 +1,174 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngCgbiProcessorTests +{ + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(7)] + [InlineData(8)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(31)] + [InlineData(32)] + [InlineData(33)] + [InlineData(64)] + public void ApplyTransform_RgbWithAlpha_MatchesScalar(int pixelCount) + { + // Drives the full V512/V256/V128/scalar dispatch, so it covers each + // path that is hardware-accelerated on the host plus the scalar tail. + byte[] input = CreateBgraScanline(pixelCount); + byte[] processorOutput = (byte[])input.Clone(); + byte[] scalarOutput = (byte[])input.Clone(); + + PngCgbiProcessor.ApplyTransform(Configuration.Default, processorOutput, PngColorType.RgbWithAlpha); + ApplyCgbiTransformScalarReference(scalarOutput); + + Assert.Equal(scalarOutput, processorOutput); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(7)] + [InlineData(8)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(31)] + [InlineData(32)] + [InlineData(33)] + [InlineData(64)] + public void ApplyTransformVector512_MatchesScalar(int pixelCount) => + // Vector512 uses Vector512_.ShuffleNative which falls back to the software + // Vector512.Shuffle when Avx512BW is unavailable, so the body runs regardless + // of whether Vector512 is hardware-accelerated on the host. + AssertVectorMatchesScalar( + pixelCount, + scanline => PngCgbiProcessor.ApplyTransformVector512(scanline, scanline.Length / 4), + blockSize: 16); + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(7)] + [InlineData(8)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(31)] + [InlineData(32)] + [InlineData(64)] + public void ApplyTransformVector256_MatchesScalar(int pixelCount) => AssertVectorMatchesScalar( + pixelCount, + scanline => PngCgbiProcessor.ApplyTransformVector256(scanline, 0, scanline.Length / 4), + blockSize: 8); + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(7)] + [InlineData(8)] + [InlineData(15)] + [InlineData(16)] + [InlineData(64)] + public void ApplyTransformVector128_MatchesScalar(int pixelCount) => AssertVectorMatchesScalar( + pixelCount, + scanline => PngCgbiProcessor.ApplyTransformVector128(scanline, 0, scanline.Length / 4), + blockSize: 4); + + private static void AssertVectorMatchesScalar(int pixelCount, Func applyVector, int blockSize) + { + byte[] input = CreateBgraScanline(pixelCount); + byte[] vectorOutput = (byte[])input.Clone(); + byte[] scalarOutput = (byte[])input.Clone(); + + int processed = applyVector(vectorOutput); + + int expectedProcessed = (pixelCount / blockSize) * blockSize; + Assert.Equal(expectedProcessed, processed); + + // The vector path is responsible for whole blocks only; remaining pixels are + // handled by the scalar tail in ApplyTransform. Run the scalar reference + // over every pixel and compare the prefix the vector path actually wrote. + ApplyCgbiTransformScalarReference(scalarOutput); + + Span vectorProcessed = vectorOutput.AsSpan(0, processed * 4); + Span scalarProcessed = scalarOutput.AsSpan(0, processed * 4); + Assert.True(vectorProcessed.SequenceEqual(scalarProcessed), $"Mismatch at pixelCount={pixelCount}"); + + // Pixels past the vector's processed prefix must be untouched. + Span vectorTail = vectorOutput.AsSpan(processed * 4); + Span inputTail = input.AsSpan(processed * 4); + Assert.True(vectorTail.SequenceEqual(inputTail)); + } + + private static byte[] CreateBgraScanline(int pixelCount) + { + // Deterministic mix of edge cases (a=0, a=255, partial alpha) and varied channels. + byte[] bytes = new byte[pixelCount * 4]; + for (int p = 0; p < pixelCount; p++) + { + byte a = (p % 7) switch + { + 0 => byte.MinValue, + 1 => byte.MaxValue, + _ => (byte)((((p * 37) + 23) & 0xFF) | 1) // never zero + }; + + // CgBI premultiplied BGRA: c' = c * a / 255 + byte r = (byte)((p * 13) & 0xFF); + byte g = (byte)((p * 29) & 0xFF); + byte b = (byte)((p * 53) & 0xFF); + r = (byte)((r * a) / byte.MaxValue); + g = (byte)((g * a) / byte.MaxValue); + b = (byte)((b * a) / byte.MaxValue); + + bytes[(p * 4) + 0] = b; + bytes[(p * 4) + 1] = g; + bytes[(p * 4) + 2] = r; + bytes[(p * 4) + 3] = a; + } + + return bytes; + } + + private static void ApplyCgbiTransformScalarReference(Span scanline) + { + Span pixels = MemoryMarshal.Cast(scanline); + for (int i = 0; i < pixels.Length; i++) + { + ref Rgba32 pixel = ref pixels[i]; + pixel = new Rgba32(pixel.B, pixel.G, pixel.R, pixel.A); + + byte a = pixel.A; + if (a is 0 or byte.MaxValue) + { + continue; + } + + int half = a >> 1; + byte r = (byte)Math.Min(byte.MaxValue, ((pixel.R * byte.MaxValue) + half) / a); + byte g = (byte)Math.Min(byte.MaxValue, ((pixel.G * byte.MaxValue) + half) / a); + byte b = (byte)Math.Min(byte.MaxValue, ((pixel.B * byte.MaxValue) + half) / a); + pixel = new Rgba32(r, g, b, a); + } + } +} From 9dfc0ca044e96b1109d506cd82911f548a5e33af Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 14:45:32 +0200 Subject: [PATCH 190/240] Remove unnecessary instruction set check --- src/ImageSharp/Formats/Png/PngCgbiProcessor.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs index 6478acced..73af0253e 100644 --- a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -47,10 +47,7 @@ internal static class PngCgbiProcessor Span pixels = MemoryMarshal.Cast(scanline); int i = 0; - // Avx512BW is required for the per-byte vpshufb. Without it, ShuffleNative - // falls back to a software cross-lane shuffle that is slower than the V256 - // path, so skip V512 entirely on Avx512F-only hosts. - if (Vector512.IsHardwareAccelerated && Avx512BW.IsSupported && pixels.Length >= 16) + if (Vector512.IsHardwareAccelerated && pixels.Length >= 16) { i = ApplyTransformVector512(scanline, pixels.Length); } @@ -102,8 +99,9 @@ internal static class PngCgbiProcessor ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); int i = 0; - // The mask only swaps bytes inside each 4-byte pixel, so it is correct - // for the per-lane Avx512BW.Shuffle that ShuffleNative selects here. + // Indices stay within their own 4-byte pixel, so the per-pixel pattern + // is also valid under the per-128-bit-lane vpshufb that ShuffleNative + // selects on AVX-512BW hosts. Vector512 shuffleMask = BgraToRgbaShuffle512; Vector512 zero = Vector512.Zero; From e6f09d66167acae42f180d566e21acac99fe0ffc Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 15:00:07 +0200 Subject: [PATCH 191/240] Use constant or static values where possible --- .../Formats/Png/PngCgbiProcessor.cs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs index 73af0253e..749e4a682 100644 --- a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -47,17 +47,17 @@ internal static class PngCgbiProcessor Span pixels = MemoryMarshal.Cast(scanline); int i = 0; - if (Vector512.IsHardwareAccelerated && pixels.Length >= 16) + if (Vector512.IsHardwareAccelerated && pixels.Length >= Vector512.Count) { i = ApplyTransformVector512(scanline, pixels.Length); } - if (Vector256.IsHardwareAccelerated && Avx2.IsSupported && (pixels.Length - i) >= 8) + if (Vector256.IsHardwareAccelerated && Avx2.IsSupported && (pixels.Length - i) >= Vector256.Count) { i = ApplyTransformVector256(scanline, i, pixels.Length); } - if (Vector128.IsHardwareAccelerated && (pixels.Length - i) >= 4) + if (Vector128.IsHardwareAccelerated && (pixels.Length - i) >= Vector128.Count) { i = ApplyTransformVector128(scanline, i, pixels.Length); } @@ -106,11 +106,9 @@ internal static class PngCgbiProcessor Vector512 zero = Vector512.Zero; Vector512 one = Vector512.One; - Vector512 byteMask = Vector512.Create(0xFF); - Vector512 opaque = Vector512.Create(0xFF); Vector512 byteMax = Vector512.Create((int)byte.MaxValue); - for (; i <= pixelCount - 16; i += 16) + for (; i <= pixelCount - Vector512.Count; i += Vector512.Count) { ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); Vector512 bgra = Unsafe.ReadUnaligned>(ref blockRef); @@ -121,11 +119,11 @@ internal static class PngCgbiProcessor // Fully transparent and fully opaque pixels are identity cases for // unpremultiplication. Masking them keeps the scalar behavior and lets // safeAlpha avoid dividing by zero for alpha == 0. - Vector512 partialMask = ~(Vector512.Equals(alpha, zero) | Vector512.Equals(alpha, opaque)); + Vector512 partialMask = ~(Vector512.Equals(alpha, zero) | Vector512.Equals(alpha, byteMax)); - Vector512 r = packed & byteMask; - Vector512 g = Vector512.ShiftRightLogical(packed, 8) & byteMask; - Vector512 b = Vector512.ShiftRightLogical(packed, 16) & byteMask; + Vector512 r = packed & byteMax; + Vector512 g = Vector512.ShiftRightLogical(packed, 8) & byteMax; + Vector512 b = Vector512.ShiftRightLogical(packed, 16) & byteMax; Vector512 safeAlpha = Vector512.ConditionalSelect(partialMask, alpha, one); Vector512 halfAlpha = Vector512.ShiftRightLogical(safeAlpha, 1); @@ -180,11 +178,9 @@ internal static class PngCgbiProcessor Vector256 zero = Vector256.Zero; Vector256 one = Vector256.One; - Vector256 byteMask = Vector256.Create(0xFF); - Vector256 opaque = Vector256.Create(0xFF); Vector256 byteMax = Vector256.Create((int)byte.MaxValue); - for (; i <= pixelCount - 8; i += 8) + for (; i <= pixelCount - Vector256.Count; i += Vector256.Count) { ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); Vector256 bgra = Unsafe.ReadUnaligned>(ref blockRef); @@ -195,11 +191,11 @@ internal static class PngCgbiProcessor // Fully transparent and fully opaque pixels are identity cases for // unpremultiplication. Masking them keeps the scalar behavior and lets // safeAlpha avoid dividing by zero for alpha == 0. - Vector256 partialMask = ~(Vector256.Equals(alpha, zero) | Vector256.Equals(alpha, opaque)); + Vector256 partialMask = ~(Vector256.Equals(alpha, zero) | Vector256.Equals(alpha, byteMax)); - Vector256 r = packed & byteMask; - Vector256 g = Vector256.ShiftRightLogical(packed, 8) & byteMask; - Vector256 b = Vector256.ShiftRightLogical(packed, 16) & byteMask; + Vector256 r = packed & byteMax; + Vector256 g = Vector256.ShiftRightLogical(packed, 8) & byteMax; + Vector256 b = Vector256.ShiftRightLogical(packed, 16) & byteMax; Vector256 safeAlpha = Vector256.ConditionalSelect(partialMask, alpha, one); Vector256 halfAlpha = Vector256.ShiftRightLogical(safeAlpha, 1); @@ -251,11 +247,9 @@ internal static class PngCgbiProcessor Vector128 zero = Vector128.Zero; Vector128 one = Vector128.One; - Vector128 byteMask = Vector128.Create(0xFF); - Vector128 opaque = Vector128.Create(0xFF); Vector128 byteMax = Vector128.Create((int)byte.MaxValue); - for (; i <= pixelCount - 4; i += 4) + for (; i <= pixelCount - Vector128.Count; i += Vector128.Count) { ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf()); Vector128 bgra = Unsafe.ReadUnaligned>(ref blockRef); @@ -266,11 +260,11 @@ internal static class PngCgbiProcessor // Fully transparent and fully opaque pixels are identity cases for // unpremultiplication. Masking them keeps the scalar behavior and lets // safeAlpha avoid dividing by zero for alpha == 0. - Vector128 partialMask = ~(Vector128.Equals(alpha, zero) | Vector128.Equals(alpha, opaque)); + Vector128 partialMask = ~(Vector128.Equals(alpha, zero) | Vector128.Equals(alpha, byteMax)); - Vector128 r = packed & byteMask; - Vector128 g = Vector128.ShiftRightLogical(packed, 8) & byteMask; - Vector128 b = Vector128.ShiftRightLogical(packed, 16) & byteMask; + Vector128 r = packed & byteMax; + Vector128 g = Vector128.ShiftRightLogical(packed, 8) & byteMax; + Vector128 b = Vector128.ShiftRightLogical(packed, 16) & byteMax; Vector128 safeAlpha = Vector128.ConditionalSelect(partialMask, alpha, one); Vector128 halfAlpha = Vector128.ShiftRightLogical(safeAlpha, 1); @@ -315,9 +309,10 @@ internal static class PngCgbiProcessor private static byte[] BuildShuffleBytes() { - byte[] bytes = new byte[64]; + byte[] bytes = new byte[Vector512.Count]; Span span = bytes; Shuffle.MMShuffleSpan(ref span, Shuffle.MMShuffle3012); + return bytes; } } From 49dc1951db9c6a8f14cae6985005e37588a16726 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 16:16:59 +0200 Subject: [PATCH 192/240] Fix read stream byte read semantics --- .../Compression/Zlib/ChunkedReadStream.cs | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs b/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs index 8c5189d4a..b697327ff 100644 --- a/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs +++ b/src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs @@ -57,57 +57,51 @@ internal sealed class ChunkedReadStream : Stream /// public override int ReadByte() - { - this.currentDataRemaining--; - return this.innerStream.ReadByte(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) { if (this.currentDataRemaining is 0) { - // Current segment is exhausted; ask the caller for the next one. this.currentDataRemaining = this.getData(); - if (this.currentDataRemaining is 0) { - return 0; + return -1; } } - int bytesToRead = Math.Min(count, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - long innerStreamLength = this.innerStream.Length; - - // Keep reading data until we've reached the end of the stream or filled the buffer. - int bytesRead = 0; - offset += totalBytesRead; - while (this.currentDataRemaining is 0 && totalBytesRead < count) + int value = this.innerStream.ReadByte(); + if (value is not -1) { - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining is 0) - { - return totalBytesRead; - } + this.currentDataRemaining--; + } - offset += bytesRead; + return value; + } - if (offset >= innerStreamLength || offset >= count) + /// + public override int Read(byte[] buffer, int offset, int count) + { + // Decrement currentDataRemaining only by bytes actually returned by + // innerStream.Read; a short read otherwise underflows the segment + // counter and triggers getData() before the segment is truly drained. + int totalBytesRead = 0; + while (totalBytesRead < count) + { + if (this.currentDataRemaining is 0) { - return totalBytesRead; + this.currentDataRemaining = this.getData(); + if (this.currentDataRemaining is 0) + { + break; + } } - bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - if (bytesRead == 0) + int bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); + int bytesRead = this.innerStream.Read(buffer, offset + totalBytesRead, bytesToRead); + if (bytesRead is 0) { - return totalBytesRead; + break; } + this.currentDataRemaining -= bytesRead; totalBytesRead += bytesRead; } From 3b37d5552d7ec554b7fbf76aec9f5fa0cac9e98e Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 16:41:04 +0200 Subject: [PATCH 193/240] Remove redundant floor. Float to int conversion will already truncate toward zero --- .../Formats/Png/PngCgbiProcessor.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs index 749e4a682..9243896de 100644 --- a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -129,20 +129,20 @@ internal static class PngCgbiProcessor Vector512 halfAlpha = Vector512.ShiftRightLogical(safeAlpha, 1); Vector512 safeAlphaF = Vector512.ConvertToSingle(safeAlpha); - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. + // ConvertToInt32 truncates toward zero (cvttps2dq / fcvtzs); since + // every quotient here is non-negative, that matches the scalar + // ((c * 255) + (a >> 1)) / a integer-division floor. Vector512 unpremultipliedR = Vector512.Min( byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + Vector512.ConvertToInt32(Vector512.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF)); Vector512 unpremultipliedG = Vector512.Min( byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + Vector512.ConvertToInt32(Vector512.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF)); Vector512 unpremultipliedB = Vector512.Min( byteMax, - Vector512.ConvertToInt32(Vector512.Floor(Vector512.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + Vector512.ConvertToInt32(Vector512.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF)); // ConditionalSelect applies the expensive unpremultiply only to pixels // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the @@ -201,20 +201,20 @@ internal static class PngCgbiProcessor Vector256 halfAlpha = Vector256.ShiftRightLogical(safeAlpha, 1); Vector256 safeAlphaF = Vector256.ConvertToSingle(safeAlpha); - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. + // ConvertToInt32 truncates toward zero (cvttps2dq / fcvtzs); since + // every quotient here is non-negative, that matches the scalar + // ((c * 255) + (a >> 1)) / a integer-division floor. Vector256 unpremultipliedR = Vector256.Min( byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + Vector256.ConvertToInt32(Vector256.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF)); Vector256 unpremultipliedG = Vector256.Min( byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + Vector256.ConvertToInt32(Vector256.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF)); Vector256 unpremultipliedB = Vector256.Min( byteMax, - Vector256.ConvertToInt32(Vector256.Floor(Vector256.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + Vector256.ConvertToInt32(Vector256.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF)); // ConditionalSelect applies the expensive unpremultiply only to pixels // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the @@ -270,20 +270,20 @@ internal static class PngCgbiProcessor Vector128 halfAlpha = Vector128.ShiftRightLogical(safeAlpha, 1); Vector128 safeAlphaF = Vector128.ConvertToSingle(safeAlpha); - // The scalar path computes ((c * 255) + (a >> 1)) / a with integer - // division. Floor the positive quotient before converting so SIMD does - // not use the default round-to-nearest conversion and drift by one. + // ConvertToInt32 truncates toward zero (cvttps2dq / fcvtzs); since + // every quotient here is non-negative, that matches the scalar + // ((c * 255) + (a >> 1)) / a integer-division floor. Vector128 unpremultipliedR = Vector128.Min( byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF))); + Vector128.ConvertToInt32(Vector128.ConvertToSingle((r * byteMax) + halfAlpha) / safeAlphaF)); Vector128 unpremultipliedG = Vector128.Min( byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF))); + Vector128.ConvertToInt32(Vector128.ConvertToSingle((g * byteMax) + halfAlpha) / safeAlphaF)); Vector128 unpremultipliedB = Vector128.Min( byteMax, - Vector128.ConvertToInt32(Vector128.Floor(Vector128.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF))); + Vector128.ConvertToInt32(Vector128.ConvertToSingle((b * byteMax) + halfAlpha) / safeAlphaF)); // ConditionalSelect applies the expensive unpremultiply only to pixels // where alpha is between 1 and 254; alpha 0 and 255 lanes keep the From 47678c748df268ceebd07fae8364af046047ec69 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 27 May 2026 16:47:43 +0200 Subject: [PATCH 194/240] Use single buffer for shuffle bytes --- src/ImageSharp/Formats/Png/PngCgbiProcessor.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs index 9243896de..e4847ad67 100644 --- a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -26,13 +26,14 @@ internal static class PngCgbiProcessor { // Per-pixel byte indices that swap CgBI's BGRA layout to Rgba32's RGBA. // MMShuffle3012 expands to [2, 1, 0, 3] per 4-byte pixel; the same 64-byte - // sequence seeds all three shuffle masks (V128/V256 ctors take the leading - // 16/32 bytes). - private static readonly Vector128 BgraToRgbaShuffle128 = Vector128.Create(BuildShuffleBytes()); + // sequence seeds all three shuffle masks (Vector128/256 take a leading slice). + private static readonly byte[] BgraToRgbaShuffleBytes = BuildShuffleBytes(); - private static readonly Vector256 BgraToRgbaShuffle256 = Vector256.Create(BuildShuffleBytes()); + private static readonly Vector128 BgraToRgbaShuffle128 = Vector128.Create(new ReadOnlySpan(BgraToRgbaShuffleBytes, 0, Vector128.Count)); - private static readonly Vector512 BgraToRgbaShuffle512 = Vector512.Create(BuildShuffleBytes()); + private static readonly Vector256 BgraToRgbaShuffle256 = Vector256.Create(new ReadOnlySpan(BgraToRgbaShuffleBytes, 0, Vector256.Count)); + + private static readonly Vector512 BgraToRgbaShuffle512 = Vector512.Create(BgraToRgbaShuffleBytes); /// /// Applies the inverse of Apple's CgBI pixel mangling to a defiltered scanline in place. From 3d5083419325a5ea305b62d6f6d4d082017fb4b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 09:49:56 +0000 Subject: [PATCH 195/240] Bump codecov/codecov-action from 6 to 7 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v6...v7) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/code-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index fe2e4e5ca..39d30ea45 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -93,7 +93,7 @@ jobs: path: tests/Images/ActualOutput/ - name: Codecov Update - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests From e5ca2782e67a954d8f3ec253a76311d265cb639d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 11 Jun 2026 11:01:29 +1000 Subject: [PATCH 196/240] Fix GIF transparency handling and dither --- src/ImageSharp/Color/Color.WernerPalette.cs | 6 +++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 31 +++++++++++++------ .../Processors/Dithering/ErrorDither.cs | 9 ++++++ .../Processors/Dithering/OrderedDither.cs | 10 ++++++ .../Formats/Gif/GifEncoderTests.cs | 20 +++++++++++- tests/ImageSharp.Tests/TestImages.cs | 4 ++- .../00.gif | 3 ++ .../08.gif | 3 ++ .../104.gif | 3 ++ .../112.gif | 3 ++ .../16.gif | 3 ++ .../24.gif | 3 ++ .../32.gif | 3 ++ .../40.gif | 3 ++ .../48.gif | 3 ++ .../56.gif | 3 ++ .../64.gif | 3 ++ .../72.gif | 3 ++ .../80.gif | 3 ++ .../88.gif | 3 ++ .../96.gif | 3 ++ ...zed_Encode_Artifacts_Rgba32_issue_2469.png | 4 +-- ...antized_Encode_Alpha_Rgba32_Issue_2668.png | 4 +-- tests/Images/Input/Gif/issues/issue_3142.gif | 3 ++ 24 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif create mode 100644 tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif create mode 100644 tests/Images/Input/Gif/issues/issue_3142.gif diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs index 583c71379..f6b625625 100644 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -127,6 +127,10 @@ public partial struct Color ParseHex("#8b7859"), ParseHex("#9b856b"), ParseHex("#766051"), - ParseHex("#453b32") + ParseHex("#453b32"), + + // Werner does not define a transparent color, but we need to add one to + // make the palette work with the rest of the library. + Transparent ]; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index dcba3953d..344c13cc9 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -361,7 +361,10 @@ internal sealed class GifEncoderCore : Color.Transparent; // Deduplicate and quantize the frame capturing only required parts. - (bool difference, Rectangle bounds) = + // Pixels matching the previous frame are replaced with the transparent placeholder. + // When the entire frame matches there is no captured difference, but every pixel is + // still a placeholder, so a transparent index is always required for additional frames. + (_, Rectangle bounds) = AnimationUtilities.DeDuplicatePixels( this.configuration, previous, @@ -378,7 +381,7 @@ internal sealed class GifEncoderCore bounds, metadata, useLocal, - difference, + true, transparencyIndex, background); @@ -403,7 +406,7 @@ internal sealed class GifEncoderCore Rectangle bounds, GifFrameMetadata metadata, bool useLocal, - bool hasDuplicates, + bool requiresTransparency, int transparencyIndex, Color transparentColor) where TPixel : unmanaged, IPixel @@ -417,9 +420,11 @@ internal sealed class GifEncoderCore // We can use the color data from the decoded metadata here. // We avoid dithering by default to preserve the original colors. ReadOnlyMemory palette = metadata.LocalColorTable.Value; - if (hasDuplicates && !metadata.HasTransparency) + if (requiresTransparency && !metadata.HasTransparency) { - // Duplicates were captured but the metadata does not have transparency. + // The frame was de-duplicated against the previous frame, replacing matching + // pixels with the transparent placeholder, but the metadata does not yet carry + // a transparent index. Reserve one so those pixels encode as transparent. metadata.HasTransparency = true; if (palette.Length < 256) @@ -480,7 +485,7 @@ internal sealed class GifEncoderCore metadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex); - if (hasDuplicates) + if (requiresTransparency) { metadata.HasTransparency = true; } @@ -492,11 +497,19 @@ internal sealed class GifEncoderCore // Individual frames, though using the shared palette, can use a different transparent index // to represent transparency. - // A difference was captured but the metadata does not have transparency. - if (hasDuplicates && !metadata.HasTransparency) + // The frame was de-duplicated against the previous frame, replacing matching pixels with + // the transparent placeholder. When the whole frame matches there is no captured difference, + // yet every pixel is still a placeholder, so we must always reserve a transparent index here; + // otherwise the placeholder pixels are matched to the nearest (typically darkest) palette color. + if (requiresTransparency && !metadata.HasTransparency) { metadata.HasTransparency = true; - transparencyIndex = globalFrameQuantizer.Palette.Length; + + // Normally we pad one index past the palette so the (out of range) value is treated as + // transparent by decoders without growing the color table. A full 256-color palette leaves + // no room to pad within the 8-bit index space (index 256 wraps to 0 when written and exceeds + // the maximum GIF bit depth), so reuse the last in-range index for transparency instead. + transparencyIndex = Math.Min(globalFrameQuantizer.Palette.Length, byte.MaxValue); metadata.TransparencyIndex = ClampIndex(transparencyIndex); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 3c0ebb707..115db8144 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -202,6 +202,15 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I ref TPixel pixel = ref rowSpan[targetX]; Vector4 result = pixel.ToVector4(); + // 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, + // for indexed transparency, nudges them off the exact transparent color so they are + // matched to the nearest opaque palette entry instead of being kept transparent. + if (result.W <= 0) + { + continue; + } + result += error * coefficient; pixel = TPixel.FromVector4(result); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index b0622cac5..cec4874f6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -182,6 +182,16 @@ public readonly partial struct OrderedDither : IDither, IEquatable { Rgba32 rgba = source.ToRgba32(); + + // Leave fully transparent pixels untouched. They carry no visible color (a decoder shows + // whatever is behind them), so perturbing them is meaningless and, for indexed transparency, + // nudges them off the exact transparent color so they are matched to the nearest opaque + // palette entry instead of being kept transparent. + if (rgba.A == 0) + { + return source; + } + Unsafe.SkipInit(out Rgba32 attempt); float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index b7bbe4971..a465b073b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -349,7 +350,7 @@ public class GifEncoderTests public static string[] Animated => TestImages.Gif.Animated; - [Theory(Skip = "Enable for visual animated testing")] + [Theory]//(Skip = "Enable for visual animated testing")] [WithFileCollection(nameof(Animated), PixelTypes.Rgba32)] public void Encode_Animated_VisualTest(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -436,4 +437,21 @@ public class GifEncoderTests static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); } + + [Theory] + [WithFile(TestImages.Gif.Issues.Issue3142, PixelTypes.Rgba32)] + public void GifEncoder_CanDecode_AndEncode_Issue3142(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // Save the image for visual inspection. + provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder() { Quantizer = KnownQuantizers.Wu }, "animated"); + + // Now compare the debug output with the reference output. + // We do this because the gif encoding is lossy and encoding will lead to differences in the 10s of percent. + // From the unencoded image, we can see that the image is visually the same. + static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. + image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1b6ae5685..2e17c82dc 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -614,6 +614,7 @@ public static class TestImages public const string Issue2859_B = "Gif/issues/issue_2859_B.gif"; public const string Issue2953 = "Gif/issues/issue_2953.gif"; public const string Issue2980 = "Gif/issues/issue_2980.gif"; + public const string Issue3142 = "Gif/issues/issue_3142.gif"; } public static readonly string[] Animated = @@ -635,7 +636,8 @@ public static class TestImages Issues.BadDescriptorWidth, Issues.Issue1530, Bit18RGBCube, - Global256NoTrans + Global256NoTrans, + Issues.Issue3142 ]; } diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif new file mode 100644 index 000000000..4a2dcec8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba81598dc214845f18064f987a55eebdab0e53149df8fd67904733b42c760ad6 +size 7389 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif new file mode 100644 index 000000000..4a2dcec8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba81598dc214845f18064f987a55eebdab0e53149df8fd67904733b42c760ad6 +size 7389 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif new file mode 100644 index 000000000..a28b1dd60 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6bd47e66b07bab7edf53a07631016f8334ba337243d6b8d61b0c8a3cf7f3f0d +size 7974 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif new file mode 100644 index 000000000..a28b1dd60 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6bd47e66b07bab7edf53a07631016f8334ba337243d6b8d61b0c8a3cf7f3f0d +size 7974 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif new file mode 100644 index 000000000..016f9f9b6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03d4e5cdaed2def942ca8480c9e45bb3d7914c6dbe3eac2b287afc8ff815b95d +size 8705 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif new file mode 100644 index 000000000..285514e23 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ad084d64942ea1dec0c1b22a88400b47f14145d761aa22a8e7deb42b5bd9fb0 +size 13011 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif new file mode 100644 index 000000000..34eff6c6c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:714cf4be4fc6c4164494ca1950796211577ab61cbd939f7cc53a36c3f7f742c5 +size 16457 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif new file mode 100644 index 000000000..7a732b4fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb04c45837b763abf2c326b3e831c5b772853d50c0bfc85fb7ac7ca62361292d +size 16872 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif new file mode 100644 index 000000000..7a732b4fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb04c45837b763abf2c326b3e831c5b772853d50c0bfc85fb7ac7ca62361292d +size 16872 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif new file mode 100644 index 000000000..6d60d4981 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c68bad5aebb65e86e4295956b9f0bc21e181a6d8dadd9dbfbd5febf0a5ba0d8 +size 17414 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif new file mode 100644 index 000000000..9af6451a6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d0672248b0e45ea700aa0c484ed0e342284ce18cad1cdc233700614636bed11 +size 16556 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif new file mode 100644 index 000000000..282ef1f8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b12d34adf8c531c0d4ab02f7b57507a585bb1d31fb82d828c8c52732cef9f18 +size 15590 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif new file mode 100644 index 000000000..282ef1f8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b12d34adf8c531c0d4ab02f7b57507a585bb1d31fb82d828c8c52732cef9f18 +size 15590 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif new file mode 100644 index 000000000..108da2ce3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31b9aee61fbc7d5dd5f163f2c81cb36140a05d788a7ed2d15de550c0fe13232 +size 14561 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif new file mode 100644 index 000000000..161443e4f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bc49ba55ee33a3aff5069b7a6bf45cdb429ad691f8701093ad7fe04abe25ea1 +size 12156 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png index ecf0691cd..c10227c6b 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:770061fbb29cd20bc700ce3fc57e38a758c632c3e89de51f5fbee3d5d522539e -size 912635 +oid sha256:b33a960891c6b1e9cc6ac2ddc3cf49d8f49e0c749dfa7a67db49354988b129f6 +size 935007 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png index b29213870..9c679acee 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df34f8f3640b145add4f24f8003c288fe7991373b079a87b4be90842e18c82ae -size 8236 +oid sha256:ad1630f3da7c2b997d04c75de2ac1465255c977454461f2fdc7026b3f87e11f1 +size 8232 diff --git a/tests/Images/Input/Gif/issues/issue_3142.gif b/tests/Images/Input/Gif/issues/issue_3142.gif new file mode 100644 index 000000000..6053eebcf --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue_3142.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:982c394dbee297bb3bc5cc532827742260acb6e5aa07e1dca3f0b17e57bc3bbe +size 386565 From fb207199f5b3db589f8a2013caeb72f238e6c8e7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:38:29 +0200 Subject: [PATCH 197/240] Clean up/clarify test helper method --- .../Formats/Png/PngCgbiProcessorTests.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs index 426afb6d4..cd03c5e48 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs @@ -120,34 +120,42 @@ public class PngCgbiProcessorTests Assert.True(vectorTail.SequenceEqual(inputTail)); } + /// + /// Builds synthetic input for tests. Produces inputs that exercise all three alpha cases. + /// + /// Channel values laid out as a defiltered CgBI scanline in premultiplied BGRA order private static byte[] CreateBgraScanline(int pixelCount) { - // Deterministic mix of edge cases (a=0, a=255, partial alpha) and varied channels. + // The distinct strides keep the channels from being equal to each other or constant across pixels + // So a bug that e.g. swaps two channels or reuses one channel's value doesn't accidentally pass + const int alphaCaseCount = 7; + const int redStride = 13; + const int greenStride = 29; + const int blueStride = 53; + byte[] bytes = new byte[pixelCount * 4]; for (int p = 0; p < pixelCount; p++) { - byte a = (p % 7) switch + // Cycling alpha through [0..255], and an odd partial value every pixel rotation + // ensures all three branches get covered within any scanline of 3 or more pixels + byte a = (p % alphaCaseCount) switch { 0 => byte.MinValue, 1 => byte.MaxValue, - _ => (byte)((((p * 37) + 23) & 0xFF) | 1) // never zero + _ => (byte)(((p * 37) + 23) | 1) // Produce a spread of alpha values and make sure to never get 0 }; - // CgBI premultiplied BGRA: c' = c * a / 255 - byte r = (byte)((p * 13) & 0xFF); - byte g = (byte)((p * 29) & 0xFF); - byte b = (byte)((p * 53) & 0xFF); - r = (byte)((r * a) / byte.MaxValue); - g = (byte)((g * a) / byte.MaxValue); - b = (byte)((b * a) / byte.MaxValue); - - bytes[(p * 4) + 0] = b; - bytes[(p * 4) + 1] = g; - bytes[(p * 4) + 2] = r; - bytes[(p * 4) + 3] = a; + int offset = p * 4; + bytes[offset + 0] = Premultiply((byte)(p * blueStride), a); + bytes[offset + 1] = Premultiply((byte)(p * greenStride), a); + bytes[offset + 2] = Premultiply((byte)(p * redStride), a); + bytes[offset + 3] = a; } return bytes; + + // CgBI stores channels premultiplied by alpha + static byte Premultiply(byte channel, byte alpha) => (byte)(channel * alpha / byte.MaxValue); } private static void ApplyCgbiTransformScalarReference(Span scanline) From 5472d6918a94771bdc03cfbcfd4579938ba5c46b Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:56:08 +0200 Subject: [PATCH 198/240] Use FeatureTestRunner for all instruction sets --- .../Formats/Png/PngCgbiProcessor.cs | 6 +- .../Formats/Png/PngCgbiProcessorTests.cs | 93 ++----------------- 2 files changed, 11 insertions(+), 88 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs index e4847ad67..3addbb3c0 100644 --- a/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngCgbiProcessor.cs @@ -95,7 +95,7 @@ internal static class PngCgbiProcessor pixel = new Rgba32(r, g, b, a); } - internal static int ApplyTransformVector512(Span scanline, int pixelCount) + private static int ApplyTransformVector512(Span scanline, int pixelCount) { ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); int i = 0; @@ -167,7 +167,7 @@ internal static class PngCgbiProcessor return i; } - internal static int ApplyTransformVector256(Span scanline, int startPixel, int pixelCount) + private static int ApplyTransformVector256(Span scanline, int startPixel, int pixelCount) { ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); int i = startPixel; @@ -239,7 +239,7 @@ internal static class PngCgbiProcessor return i; } - internal static int ApplyTransformVector128(Span scanline, int startPixel, int pixelCount) + private static int ApplyTransformVector128(Span scanline, int startPixel, int pixelCount) { ref byte scanlineRef = ref MemoryMarshal.GetReference(scanline); int i = startPixel; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs index cd03c5e48..863c7fc4f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Png; @@ -24,10 +25,14 @@ public class PngCgbiProcessorTests [InlineData(32)] [InlineData(33)] [InlineData(64)] - public void ApplyTransform_RgbWithAlpha_MatchesScalar(int pixelCount) + public void ApplyTransform_RgbWithAlpha_MatchesScalar(int pixelCount) => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + serialized => AssertApplyTransformMatchesScalar(FeatureTestRunner.Deserialize(serialized)), + pixelCount, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertApplyTransformMatchesScalar(int pixelCount) { - // Drives the full V512/V256/V128/scalar dispatch, so it covers each - // path that is hardware-accelerated on the host plus the scalar tail. byte[] input = CreateBgraScanline(pixelCount); byte[] processorOutput = (byte[])input.Clone(); byte[] scalarOutput = (byte[])input.Clone(); @@ -38,88 +43,6 @@ public class PngCgbiProcessorTests Assert.Equal(scalarOutput, processorOutput); } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(3)] - [InlineData(4)] - [InlineData(7)] - [InlineData(8)] - [InlineData(15)] - [InlineData(16)] - [InlineData(17)] - [InlineData(31)] - [InlineData(32)] - [InlineData(33)] - [InlineData(64)] - public void ApplyTransformVector512_MatchesScalar(int pixelCount) => - // Vector512 uses Vector512_.ShuffleNative which falls back to the software - // Vector512.Shuffle when Avx512BW is unavailable, so the body runs regardless - // of whether Vector512 is hardware-accelerated on the host. - AssertVectorMatchesScalar( - pixelCount, - scanline => PngCgbiProcessor.ApplyTransformVector512(scanline, scanline.Length / 4), - blockSize: 16); - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(3)] - [InlineData(4)] - [InlineData(7)] - [InlineData(8)] - [InlineData(15)] - [InlineData(16)] - [InlineData(17)] - [InlineData(31)] - [InlineData(32)] - [InlineData(64)] - public void ApplyTransformVector256_MatchesScalar(int pixelCount) => AssertVectorMatchesScalar( - pixelCount, - scanline => PngCgbiProcessor.ApplyTransformVector256(scanline, 0, scanline.Length / 4), - blockSize: 8); - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(3)] - [InlineData(4)] - [InlineData(7)] - [InlineData(8)] - [InlineData(15)] - [InlineData(16)] - [InlineData(64)] - public void ApplyTransformVector128_MatchesScalar(int pixelCount) => AssertVectorMatchesScalar( - pixelCount, - scanline => PngCgbiProcessor.ApplyTransformVector128(scanline, 0, scanline.Length / 4), - blockSize: 4); - - private static void AssertVectorMatchesScalar(int pixelCount, Func applyVector, int blockSize) - { - byte[] input = CreateBgraScanline(pixelCount); - byte[] vectorOutput = (byte[])input.Clone(); - byte[] scalarOutput = (byte[])input.Clone(); - - int processed = applyVector(vectorOutput); - - int expectedProcessed = (pixelCount / blockSize) * blockSize; - Assert.Equal(expectedProcessed, processed); - - // The vector path is responsible for whole blocks only; remaining pixels are - // handled by the scalar tail in ApplyTransform. Run the scalar reference - // over every pixel and compare the prefix the vector path actually wrote. - ApplyCgbiTransformScalarReference(scalarOutput); - - Span vectorProcessed = vectorOutput.AsSpan(0, processed * 4); - Span scalarProcessed = scalarOutput.AsSpan(0, processed * 4); - Assert.True(vectorProcessed.SequenceEqual(scalarProcessed), $"Mismatch at pixelCount={pixelCount}"); - - // Pixels past the vector's processed prefix must be untouched. - Span vectorTail = vectorOutput.AsSpan(processed * 4); - Span inputTail = input.AsSpan(processed * 4); - Assert.True(vectorTail.SequenceEqual(inputTail)); - } - /// /// Builds synthetic input for tests. Produces inputs that exercise all three alpha cases. /// From fd464a4725c91466277405da33a103d9b81bcddb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 22 Jun 2026 10:58:01 +1000 Subject: [PATCH 199/240] Revise SECURITY.md for clarity on security policies Updated the security policy to clarify supported versions, reporting vulnerabilities, and scope of responsibility. --- SECURITY.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..8b2e1df86 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,96 @@ +# Security Policy + +## Supported Versions + +Six Labors provides security fixes only for the latest major version of each library. + +Older major versions are end-of-life and do not receive security fixes. + +Users must upgrade to the latest major version to receive security fixes. + +| Version | Supported | +| -------------------- | --------- | +| Latest major version | Yes | +| Older major versions | No | + +Security fixes, if any, are provided at Six Labors' discretion. + +This policy does not create any obligation to provide support, maintenance services, SLAs, custom fixes, hosted services, managed services, operational monitoring, professional services, consulting, or certification of customer products. + +## Reporting a Vulnerability + +Please report suspected security vulnerabilities using GitHub private vulnerability reporting for the relevant Six Labors repository, where available. + +If GitHub private vulnerability reporting is not available for a repository, please report suspected security vulnerabilities by contacting Six Labors through the contact details published on the Six Labors website. + +Do not report security vulnerabilities through public GitHub issues. + +When reporting a vulnerability, please include as much relevant information as possible: + +* affected package and version +* target framework and runtime +* operating system +* input file or minimal reproduction, if safe to share +* expected and actual behavior +* potential security impact +* whether you believe the issue is being actively exploited + +Six Labors may review reported vulnerabilities and determine whether they are security issues affecting a supported version. + +A report may be declined or closed without action if, in Six Labors' opinion, it: + +* is not reproducible +* does not affect a supported version +* affects only an unsupported or end-of-life version +* is not a security vulnerability +* depends on unsafe, unsupported, or unintended use +* depends on a vulnerable application, environment, dependency, configuration, or deployment outside the Six Labors library itself +* lacks sufficient information for assessment +* is duplicative +* has already been fixed +* is otherwise outside the scope of this policy + +If a vulnerability is accepted, Six Labors may handle it through GitHub Security Advisories and, where appropriate, CVE assignment. + +Six Labors does not guarantee any response time, fix time, release date, advisory publication date, CVE assignment, workaround, mitigation, or particular outcome for any report. + +## Scope + +This policy applies only to security vulnerabilities in Six Labors libraries themselves. + +This policy does not apply to: + +* customer applications +* customer products +* customer deployments +* customer infrastructure +* customer data +* third-party services +* unsupported versions +* end-of-life versions +* forks or modified versions +* usage outside the documented or intended behavior of the relevant library + +Organizations using Six Labors libraries are responsible for assessing, securing, testing, monitoring, updating, and maintaining their own applications, products, deployments, infrastructure, and supply chains. + +## Cyber Resilience Act + +Six Labors libraries are general-purpose software libraries. + +They are not cybersecurity products, identity or access management systems, password managers, operating systems, browsers, firewalls, network management tools, SIEM tools, hypervisors, container runtimes, or other Cyber Resilience Act important or critical product classes. + +If a Six Labors library is treated as a product with digital elements under the Cyber Resilience Act, Six Labors assesses it as an ordinary software component. + +Organizations incorporating Six Labors libraries into products made available on the EU market are responsible for assessing and meeting their own regulatory obligations for those products, including any obligations under the Cyber Resilience Act. + +Six Labors does not provide support, maintenance services, SLAs, managed services, hosted services, operational monitoring, custom fixes, professional services, consulting, or certification of customer products. + +Security vulnerabilities in supported Six Labors libraries are handled through the GitHub Security Advisory process for the relevant repository, where appropriate. + +From 11 September 2026, if Six Labors becomes aware of credible active exploitation of a vulnerability in a supported Six Labors library, or a severe security incident affecting a supported Six Labors library, Six Labors may report the matter through the applicable Cyber Resilience Act reporting mechanism where legally required. + +## No Warranty + +Six Labors libraries are provided in accordance with their applicable license terms. + +Nothing in this policy creates any warranty, representation, guarantee, support obligation, maintenance obligation, service commitment, regulatory certification, or assumption of responsibility for any customer product, customer deployment, customer compliance obligation, or third-party system. From 13f417e40f32cde9c25ad79dde2136bf046c8299 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:42:34 +0000 Subject: [PATCH 200/240] Bump actions/checkout from 6 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 6 +++--- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5151e672f..61d6bcd2e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,7 +27,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive @@ -137,7 +137,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive @@ -227,7 +227,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 39d30ea45..0ce44fe4c 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -31,7 +31,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive From 39d5eec8865347e24ff27c9cda9e448a33c0cf65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 09:42:20 +0000 Subject: [PATCH 201/240] Bump actions/cache from 5 to 6 Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 8 ++++---- .github/workflows/code-coverage.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5151e672f..84d05501c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -49,7 +49,7 @@ jobs: run: echo "lfs_key=$LFS_KEY" >> "$GITHUB_OUTPUT" - name: Git Setup LFS Cache - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: .git/lfs key: ${{ steps.expose-key.outputs.lfs_key }} @@ -144,7 +144,7 @@ jobs: # Use the warmed key from WarmLFS. Do not recompute or recreate .lfs-assets-id here. - name: Git Setup LFS Cache - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: .git/lfs key: ${{ needs.WarmLFS.outputs.lfs_key }} @@ -157,7 +157,7 @@ jobs: uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v5 + uses: actions/cache@v6 id: nuget-cache with: path: ~/.nuget @@ -236,7 +236,7 @@ jobs: uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v5 + uses: actions/cache@v6 id: nuget-cache with: path: ~/.nuget diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 39d30ea45..ebcb9f705 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -46,7 +46,7 @@ jobs: run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id - name: Git Setup LFS Cache - uses: actions/cache@v5 + uses: actions/cache@v6 id: lfs-cache with: path: .git/lfs @@ -59,7 +59,7 @@ jobs: uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v5 + uses: actions/cache@v6 id: nuget-cache with: path: ~/.nuget From eb4bfc95eadd07575ef63488e943ed0bb9a070c8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 9 Jul 2026 21:09:52 +1000 Subject: [PATCH 202/240] Add BlendWithCoverage overloads. Optimize Rgba32 compatible shuffling. --- .../DefaultPixelBlenders.Generated.cs | 54892 +++++++++++++++- .../DefaultPixelBlenders.Generated.tt | 444 + .../PixelBlenders/PorterDuffFunctions.cs | 59 + .../PixelFormats/PixelBlender{TPixel}.cs | 461 + .../Abgr32.PixelOperations.Generated.cs | 16 +- .../Argb32.PixelOperations.Generated.cs | 16 +- .../Bgra32.PixelOperations.Generated.cs | 16 +- .../Generated/_Common.ttinclude | 45 + .../PixelFormats/PixelBlenderTests.cs | 221 + 9 files changed, 52697 insertions(+), 3473 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 883693031..05db95917 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -358,26 +358,9 @@ internal static class DefaultPixelBlenders } } } - } - - /// - /// A pixel blender that implements the "MultiplySrc" composition equation. - /// - public class MultiplySrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -389,14 +372,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -404,7 +405,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -416,34 +418,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -454,18 +469,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -473,7 +507,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -484,34 +519,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -522,6 +571,7 @@ internal static class DefaultPixelBlenders 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); @@ -541,11 +591,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -553,7 +619,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -566,6 +633,7 @@ internal static class DefaultPixelBlenders 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); @@ -578,31 +646,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -612,6 +691,7 @@ internal static class DefaultPixelBlenders 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, @@ -636,10 +716,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -647,7 +743,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -659,6 +756,7 @@ internal static class DefaultPixelBlenders 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); @@ -672,43 +770,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrc" composition equation. + /// A pixel blender that implements the "MultiplySrc" composition equation. /// - public class AddSrc : PixelBlender + public class MultiplySrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrc Instance { get; } = new AddSrc(); + public static MultiplySrc Instance { get; } = new MultiplySrc(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -728,7 +837,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -739,7 +848,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); } } } @@ -755,7 +864,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -765,14 +874,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); } } } @@ -798,7 +907,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -808,7 +917,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); } } } @@ -824,7 +933,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -833,14 +942,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); } } } @@ -876,7 +985,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -888,7 +997,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -913,7 +1022,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -924,14 +1033,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -971,7 +1080,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -982,7 +1091,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -1007,7 +1116,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1017,37 +1126,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractSrc" composition equation. - /// - public class SubtractSrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -1059,14 +1151,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1074,7 +1184,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1086,34 +1197,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -1124,18 +1248,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1143,7 +1286,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1154,34 +1298,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -1192,6 +1350,7 @@ internal static class DefaultPixelBlenders 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); @@ -1211,11 +1370,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1223,7 +1398,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1236,6 +1412,7 @@ internal static class DefaultPixelBlenders 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); @@ -1248,31 +1425,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -1282,6 +1470,7 @@ internal static class DefaultPixelBlenders 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, @@ -1306,10 +1495,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1317,7 +1522,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1329,6 +1535,7 @@ internal static class DefaultPixelBlenders 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); @@ -1342,43 +1549,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrc" composition equation. + /// A pixel blender that implements the "AddSrc" composition equation. /// - public class ScreenSrc : PixelBlender + public class AddSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); + public static AddSrc Instance { get; } = new AddSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1398,7 +1616,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1409,7 +1627,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); } } } @@ -1425,7 +1643,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1435,14 +1653,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); } } } @@ -1468,7 +1686,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -1478,7 +1696,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); } } } @@ -1494,7 +1712,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -1503,14 +1721,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); } } } @@ -1546,7 +1764,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1558,7 +1776,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -1583,7 +1801,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1594,14 +1812,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -1641,7 +1859,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1652,7 +1870,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -1677,7 +1895,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -1687,37 +1905,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenSrc" composition equation. - /// - public class DarkenSrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -1729,14 +1930,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1744,7 +1963,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1756,34 +1976,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -1794,18 +2027,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1813,7 +2065,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1824,34 +2077,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -1862,6 +2129,7 @@ internal static class DefaultPixelBlenders 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); @@ -1881,11 +2149,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1893,7 +2177,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1906,6 +2191,7 @@ internal static class DefaultPixelBlenders 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); @@ -1918,31 +2204,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -1952,6 +2249,7 @@ internal static class DefaultPixelBlenders 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, @@ -1976,10 +2274,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -1987,7 +2301,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -1999,6 +2314,7 @@ internal static class DefaultPixelBlenders 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); @@ -2012,43 +2328,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrc" composition equation. + /// A pixel blender that implements the "SubtractSrc" composition equation. /// - public class LightenSrc : PixelBlender + public class SubtractSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrc Instance { get; } = new LightenSrc(); + public static SubtractSrc Instance { get; } = new SubtractSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2068,7 +2395,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2079,7 +2406,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); } } } @@ -2095,7 +2422,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2105,14 +2432,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); } } } @@ -2138,7 +2465,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -2148,7 +2475,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); } } } @@ -2164,7 +2491,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -2173,14 +2500,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); } } } @@ -2216,7 +2543,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2228,7 +2555,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -2253,7 +2580,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2264,14 +2591,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -2311,7 +2638,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2322,7 +2649,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -2347,7 +2674,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2357,37 +2684,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrc" composition equation. - /// - public class OverlaySrc : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -2399,14 +2709,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -2414,7 +2742,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -2426,34 +2755,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -2464,18 +2806,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -2483,7 +2844,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -2494,34 +2856,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -2532,6 +2908,7 @@ internal static class DefaultPixelBlenders 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); @@ -2551,11 +2928,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -2563,7 +2956,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -2576,6 +2970,7 @@ internal static class DefaultPixelBlenders 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); @@ -2588,31 +2983,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -2622,6 +3028,7 @@ internal static class DefaultPixelBlenders 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, @@ -2646,10 +3053,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -2657,7 +3080,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -2669,6 +3093,7 @@ internal static class DefaultPixelBlenders 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); @@ -2682,43 +3107,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrc" composition equation. + /// A pixel blender that implements the "ScreenSrc" composition equation. /// - public class HardLightSrc : PixelBlender + public class ScreenSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrc Instance { get; } = new HardLightSrc(); + public static ScreenSrc Instance { get; } = new ScreenSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2738,7 +3174,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2749,7 +3185,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } } } @@ -2765,7 +3201,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2775,14 +3211,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); } } } @@ -2808,7 +3244,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -2818,7 +3254,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); } } } @@ -2834,7 +3270,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -2843,14 +3279,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); } } } @@ -2886,7 +3322,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2898,7 +3334,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -2923,7 +3359,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2934,14 +3370,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -2981,7 +3417,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -2992,7 +3428,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -3017,7 +3453,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3027,37 +3463,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalSrcAtop" composition equation. - /// - public class NormalSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -3069,14 +3488,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3084,7 +3521,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3096,34 +3534,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -3134,18 +3585,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3153,7 +3623,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3164,34 +3635,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -3202,6 +3687,7 @@ internal static class DefaultPixelBlenders 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); @@ -3221,11 +3707,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3233,7 +3735,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3246,6 +3749,7 @@ internal static class DefaultPixelBlenders 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); @@ -3258,31 +3762,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -3292,6 +3807,7 @@ internal static class DefaultPixelBlenders 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, @@ -3316,10 +3832,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3327,7 +3859,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3339,6 +3872,7 @@ internal static class DefaultPixelBlenders 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); @@ -3352,43 +3886,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// A pixel blender that implements the "DarkenSrc" composition equation. /// - public class MultiplySrcAtop : PixelBlender + public class DarkenSrc : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + public static DarkenSrc Instance { get; } = new DarkenSrc(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3408,7 +3953,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3419,7 +3964,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); } } } @@ -3435,7 +3980,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3445,14 +3990,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); } } } @@ -3478,9 +4023,41563 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { + destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrc" composition equation. + /// + public class LightenSrc : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrc" composition equation. + /// + public class OverlaySrc : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrc" composition equation. + /// + public class HardLightSrc : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcAtop" composition equation. + /// + public class NormalSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// + public class MultiplySrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// + public class AddSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// + public class SubtractSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public class ScreenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public class DarkenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public class LightenSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public class OverlaySrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public class HardLightSrcAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public class NormalSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public class MultiplySrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public class AddSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public class SubtractSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public class ScreenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public class DarkenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public class LightenSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public class OverlaySrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public class HardLightSrcOver : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public class NormalSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public class MultiplySrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public class AddSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public class SubtractSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public class ScreenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public class DarkenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public class LightenSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public class OverlaySrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public class HardLightSrcIn : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public class NormalSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public class MultiplySrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public class AddSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public class SubtractSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public class ScreenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public class DarkenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public class LightenSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public class OverlaySrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public class HardLightSrcOut : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public class NormalDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public class MultiplyDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public class AddDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public class SubtractDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public class ScreenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public class DarkenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public class LightenDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public class OverlayDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public class HardLightDest : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public class NormalDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public class MultiplyDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public class AddDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public class SubtractDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public class ScreenDestAtop : PixelBlender + { + /// + /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3488,7 +45587,110 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3499,34 +45701,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -3537,6 +45753,7 @@ internal static class DefaultPixelBlenders 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); @@ -3556,11 +45773,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3568,7 +45801,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3581,6 +45815,7 @@ internal static class DefaultPixelBlenders 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); @@ -3593,31 +45828,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -3627,6 +45873,7 @@ internal static class DefaultPixelBlenders 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, @@ -3651,10 +45898,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -3662,7 +45925,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -3674,6 +45938,7 @@ internal static class DefaultPixelBlenders 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); @@ -3687,43 +45952,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// A pixel blender that implements the "DarkenDestAtop" composition equation. /// - public class AddSrcAtop : PixelBlender + public class DarkenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrcAtop Instance { get; } = new AddSrcAtop(); + public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3743,7 +46019,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3754,7 +46030,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); } } } @@ -3770,7 +46046,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3780,14 +46056,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); } } } @@ -3813,7 +46089,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -3823,7 +46099,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); } } } @@ -3839,7 +46115,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -3848,14 +46124,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); } } } @@ -3891,7 +46167,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3903,7 +46179,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -3928,7 +46204,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3939,14 +46215,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -3986,7 +46262,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -3997,7 +46273,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4022,7 +46298,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4032,33 +46308,477 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// A pixel blender that implements the "LightenDestAtop" composition equation. /// - public class SubtractSrcAtop : PixelBlender + public class LightenDestAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + public static LightenDestAtop Instance { get; } = new LightenDestAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4078,7 +46798,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4089,7 +46809,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); } } } @@ -4105,7 +46825,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4115,14 +46835,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); } } } @@ -4148,7 +46868,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -4158,7 +46878,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); } } } @@ -4174,7 +46894,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -4183,14 +46903,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); } } } @@ -4226,7 +46946,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4238,7 +46958,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4263,7 +46983,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4274,14 +46994,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4321,7 +47041,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4332,7 +47052,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4357,7 +47077,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4367,37 +47087,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -4409,14 +47112,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -4424,7 +47145,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -4436,34 +47158,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -4474,18 +47209,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -4493,7 +47247,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -4504,34 +47259,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -4542,6 +47311,7 @@ internal static class DefaultPixelBlenders 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); @@ -4561,11 +47331,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -4573,7 +47359,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -4586,6 +47373,7 @@ internal static class DefaultPixelBlenders 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); @@ -4598,31 +47386,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -4632,6 +47431,7 @@ internal static class DefaultPixelBlenders 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, @@ -4656,10 +47456,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -4667,7 +47483,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -4679,6 +47496,7 @@ internal static class DefaultPixelBlenders 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); @@ -4692,43 +47510,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// A pixel blender that implements the "OverlayDestAtop" composition equation. /// - public class DarkenSrcAtop : PixelBlender + public class OverlayDestAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4748,7 +47577,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4759,7 +47588,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); } } } @@ -4775,7 +47604,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4785,14 +47614,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); } } } @@ -4818,7 +47647,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -4828,7 +47657,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); } } } @@ -4844,7 +47673,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -4853,14 +47682,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); } } } @@ -4896,7 +47725,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4908,7 +47737,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4933,7 +47762,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -4944,14 +47773,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -4991,7 +47820,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5002,7 +47831,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -5027,7 +47856,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5037,37 +47866,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -5079,14 +47891,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5094,7 +47924,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5106,34 +47937,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -5144,18 +47988,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5163,7 +48026,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5174,34 +48038,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -5212,6 +48090,7 @@ internal static class DefaultPixelBlenders 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); @@ -5231,11 +48110,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5243,7 +48138,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5256,6 +48152,7 @@ internal static class DefaultPixelBlenders 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); @@ -5268,31 +48165,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -5302,6 +48210,7 @@ internal static class DefaultPixelBlenders 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, @@ -5326,10 +48235,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5337,7 +48262,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5349,6 +48275,7 @@ internal static class DefaultPixelBlenders 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); @@ -5362,43 +48289,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// A pixel blender that implements the "HardLightDestAtop" composition equation. /// - public class OverlaySrcAtop : PixelBlender + public class HardLightDestAtop : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5418,7 +48356,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5429,7 +48367,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); } } } @@ -5445,7 +48383,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5455,14 +48393,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); } } } @@ -5488,7 +48426,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -5498,7 +48436,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); } } } @@ -5514,7 +48452,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -5523,14 +48461,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); } } } @@ -5566,7 +48504,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5578,7 +48516,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -5603,7 +48541,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5614,14 +48552,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -5661,7 +48599,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5672,7 +48610,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -5697,7 +48635,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -5707,37 +48645,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -5749,14 +48670,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5764,7 +48703,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5776,34 +48716,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -5814,18 +48767,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5833,7 +48805,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5844,34 +48817,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -5882,6 +48869,7 @@ internal static class DefaultPixelBlenders 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); @@ -5901,11 +48889,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -5913,7 +48917,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -5926,6 +48931,7 @@ internal static class DefaultPixelBlenders 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); @@ -5938,31 +48944,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -5972,6 +48989,7 @@ internal static class DefaultPixelBlenders 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, @@ -5996,10 +49014,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -6007,7 +49041,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -6019,6 +49054,7 @@ internal static class DefaultPixelBlenders 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); @@ -6032,43 +49068,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// A pixel blender that implements the "NormalDestOver" composition equation. /// - public class NormalSrcOver : PixelBlender + public class NormalDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + public static NormalDestOver Instance { get; } = new NormalDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6088,7 +49135,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6099,7 +49146,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); } } } @@ -6115,7 +49162,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6125,14 +49172,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); } } } @@ -6158,7 +49205,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -6168,7 +49215,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); } } } @@ -6184,7 +49231,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -6193,14 +49240,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); } } } @@ -6236,7 +49283,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6248,7 +49295,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -6273,7 +49320,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6284,14 +49331,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -6331,7 +49378,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6342,7 +49389,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -6367,7 +49414,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6377,37 +49424,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. - /// - public class MultiplySrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -6419,14 +49449,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -6434,7 +49482,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -6446,34 +49495,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -6484,18 +49546,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -6503,7 +49584,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -6514,34 +49596,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -6552,6 +49648,7 @@ internal static class DefaultPixelBlenders 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); @@ -6571,11 +49668,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -6583,7 +49696,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -6596,6 +49710,7 @@ internal static class DefaultPixelBlenders 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); @@ -6608,31 +49723,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -6642,6 +49768,7 @@ internal static class DefaultPixelBlenders 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, @@ -6666,10 +49793,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -6677,7 +49820,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -6689,6 +49833,7 @@ internal static class DefaultPixelBlenders 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); @@ -6702,43 +49847,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrcOver" composition equation. + /// A pixel blender that implements the "MultiplyDestOver" composition equation. /// - public class AddSrcOver : PixelBlender + public class MultiplyDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrcOver Instance { get; } = new AddSrcOver(); + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6758,7 +49914,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6769,7 +49925,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); } } } @@ -6785,7 +49941,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6795,14 +49951,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); } } } @@ -6828,7 +49984,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -6838,7 +49994,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); } } } @@ -6854,7 +50010,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -6863,14 +50019,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); } } } @@ -6906,7 +50062,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6918,7 +50074,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -6943,7 +50099,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -6954,14 +50110,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -7001,7 +50157,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7012,7 +50168,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -7037,7 +50193,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7047,37 +50203,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. - /// - public class SubtractSrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -7089,14 +50228,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7104,7 +50261,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7116,34 +50274,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -7154,18 +50325,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7173,7 +50363,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7184,34 +50375,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -7222,6 +50427,7 @@ internal static class DefaultPixelBlenders 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); @@ -7241,11 +50447,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7253,7 +50475,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7266,6 +50489,7 @@ internal static class DefaultPixelBlenders 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); @@ -7278,31 +50502,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -7312,6 +50547,7 @@ internal static class DefaultPixelBlenders 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, @@ -7336,10 +50572,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7347,7 +50599,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7359,6 +50612,7 @@ internal static class DefaultPixelBlenders 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); @@ -7372,43 +50626,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// A pixel blender that implements the "AddDestOver" composition equation. /// - public class ScreenSrcOver : PixelBlender + public class AddDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + public static AddDestOver Instance { get; } = new AddDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7428,7 +50693,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7439,7 +50704,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); } } } @@ -7455,7 +50720,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7465,14 +50730,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); } } } @@ -7498,7 +50763,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -7508,7 +50773,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); } } } @@ -7524,7 +50789,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -7533,14 +50798,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); } } } @@ -7576,7 +50841,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7588,7 +50853,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -7613,7 +50878,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7624,14 +50889,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -7671,7 +50936,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7682,7 +50947,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -7707,7 +50972,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -7717,37 +50982,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. - /// - public class DarkenSrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -7759,14 +51007,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7774,7 +51040,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7786,34 +51053,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -7824,18 +51104,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7843,7 +51142,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7854,34 +51154,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -7892,6 +51206,7 @@ internal static class DefaultPixelBlenders 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); @@ -7911,11 +51226,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -7923,7 +51254,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -7936,6 +51268,7 @@ internal static class DefaultPixelBlenders 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); @@ -7948,31 +51281,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -7982,6 +51326,7 @@ internal static class DefaultPixelBlenders 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, @@ -8006,10 +51351,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -8017,7 +51378,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -8029,6 +51391,7 @@ internal static class DefaultPixelBlenders 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); @@ -8042,43 +51405,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// A pixel blender that implements the "SubtractDestOver" composition equation. /// - public class LightenSrcOver : PixelBlender + public class SubtractDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8098,7 +51472,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8109,7 +51483,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); } } } @@ -8125,7 +51499,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8135,14 +51509,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); } } } @@ -8168,7 +51542,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -8178,7 +51552,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); } } } @@ -8194,7 +51568,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -8203,14 +51577,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); } } } @@ -8246,7 +51620,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8258,7 +51632,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -8283,7 +51657,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8294,14 +51668,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -8341,7 +51715,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8352,7 +51726,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -8377,7 +51751,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8387,37 +51761,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -8429,14 +51786,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -8444,7 +51819,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -8456,34 +51832,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -8494,18 +51883,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -8513,7 +51921,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -8524,34 +51933,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -8562,6 +51985,7 @@ internal static class DefaultPixelBlenders 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); @@ -8581,11 +52005,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -8593,7 +52033,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -8606,6 +52047,7 @@ internal static class DefaultPixelBlenders 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); @@ -8618,31 +52060,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -8652,6 +52105,7 @@ internal static class DefaultPixelBlenders 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, @@ -8676,10 +52130,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -8687,7 +52157,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -8699,6 +52170,7 @@ internal static class DefaultPixelBlenders 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); @@ -8712,43 +52184,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// A pixel blender that implements the "ScreenDestOver" composition equation. /// - public class HardLightSrcOver : PixelBlender + public class ScreenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8768,7 +52251,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8779,7 +52262,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); } } } @@ -8795,7 +52278,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8805,14 +52288,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); } } } @@ -8838,7 +52321,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -8848,7 +52331,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); } } } @@ -8864,7 +52347,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -8873,14 +52356,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); } } } @@ -8916,7 +52399,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8928,7 +52411,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -8953,7 +52436,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -8964,14 +52447,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -9011,7 +52494,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9022,7 +52505,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -9047,7 +52530,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9057,37 +52540,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -9099,14 +52565,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9114,7 +52598,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9126,34 +52611,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -9164,18 +52662,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9183,7 +52700,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9194,34 +52712,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -9232,6 +52764,7 @@ internal static class DefaultPixelBlenders 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); @@ -9251,11 +52784,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9263,7 +52812,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9276,6 +52826,7 @@ internal static class DefaultPixelBlenders 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); @@ -9288,31 +52839,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -9322,6 +52884,7 @@ internal static class DefaultPixelBlenders 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, @@ -9346,10 +52909,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9357,7 +52936,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9369,6 +52949,7 @@ internal static class DefaultPixelBlenders 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); @@ -9382,43 +52963,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// A pixel blender that implements the "DarkenDestOver" composition equation. /// - public class MultiplySrcIn : PixelBlender + public class DarkenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9438,7 +53030,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9449,7 +53041,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); } } } @@ -9465,7 +53057,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9475,14 +53067,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); } } } @@ -9508,7 +53100,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -9518,7 +53110,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); } } } @@ -9534,7 +53126,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -9543,14 +53135,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); } } } @@ -9586,7 +53178,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9598,7 +53190,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -9623,7 +53215,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9634,14 +53226,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -9681,7 +53273,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9692,7 +53284,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -9717,7 +53309,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -9727,37 +53319,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -9769,14 +53344,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9784,7 +53377,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9796,34 +53390,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -9834,18 +53441,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9853,7 +53479,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9864,34 +53491,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -9902,6 +53543,7 @@ internal static class DefaultPixelBlenders 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); @@ -9921,11 +53563,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -9933,7 +53591,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -9946,6 +53605,7 @@ internal static class DefaultPixelBlenders 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); @@ -9958,31 +53618,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -9992,6 +53663,7 @@ internal static class DefaultPixelBlenders 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, @@ -10016,10 +53688,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -10027,7 +53715,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -10039,6 +53728,7 @@ internal static class DefaultPixelBlenders 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); @@ -10052,43 +53742,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// A pixel blender that implements the "LightenDestOver" composition equation. /// - public class SubtractSrcIn : PixelBlender + public class LightenDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + public static LightenDestOver Instance { get; } = new LightenDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10108,7 +53809,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10119,7 +53820,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); } } } @@ -10135,7 +53836,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10145,14 +53846,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); } } } @@ -10178,7 +53879,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -10188,7 +53889,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); } } } @@ -10204,7 +53905,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -10213,14 +53914,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); } } } @@ -10256,7 +53957,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10268,7 +53969,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -10293,7 +53994,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10304,14 +54005,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -10351,7 +54052,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10362,7 +54063,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -10387,7 +54088,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10397,37 +54098,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -10439,14 +54123,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -10454,7 +54156,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -10466,34 +54169,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -10504,18 +54220,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -10523,7 +54258,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -10534,34 +54270,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -10572,6 +54322,7 @@ internal static class DefaultPixelBlenders 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); @@ -10591,11 +54342,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -10603,7 +54370,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -10616,6 +54384,7 @@ internal static class DefaultPixelBlenders 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); @@ -10628,31 +54397,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -10662,6 +54442,7 @@ internal static class DefaultPixelBlenders 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, @@ -10686,10 +54467,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -10697,7 +54494,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -10709,6 +54507,7 @@ internal static class DefaultPixelBlenders 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); @@ -10722,43 +54521,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// A pixel blender that implements the "OverlayDestOver" composition equation. /// - public class DarkenSrcIn : PixelBlender + public class OverlayDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10778,7 +54588,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10789,7 +54599,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); } } } @@ -10805,7 +54615,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10815,14 +54625,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); } } } @@ -10848,7 +54658,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -10858,7 +54668,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); } } } @@ -10874,7 +54684,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -10883,14 +54693,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); } } } @@ -10926,7 +54736,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10938,7 +54748,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -10963,7 +54773,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -10974,14 +54784,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -11021,7 +54831,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11032,7 +54842,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -11057,7 +54867,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11067,37 +54877,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -11109,14 +54902,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11124,7 +54935,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11136,34 +54948,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -11174,18 +54999,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11193,7 +55037,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11204,34 +55049,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -11242,6 +55101,7 @@ internal static class DefaultPixelBlenders 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); @@ -11261,11 +55121,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11273,7 +55149,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11286,6 +55163,7 @@ internal static class DefaultPixelBlenders 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); @@ -11298,31 +55176,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -11332,6 +55221,7 @@ internal static class DefaultPixelBlenders 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, @@ -11356,10 +55246,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11367,7 +55273,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11379,6 +55286,7 @@ internal static class DefaultPixelBlenders 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); @@ -11392,43 +55300,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// A pixel blender that implements the "HardLightDestOver" composition equation. /// - public class OverlaySrcIn : PixelBlender + public class HardLightDestOver : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -11448,7 +55367,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11459,7 +55378,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); } } } @@ -11475,7 +55394,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11485,14 +55404,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); } } } @@ -11518,7 +55437,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -11528,7 +55447,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); } } } @@ -11544,7 +55463,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -11553,14 +55472,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); } } } @@ -11596,7 +55515,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11608,7 +55527,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -11633,7 +55552,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11644,14 +55563,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -11691,7 +55610,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11702,7 +55621,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -11727,7 +55646,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -11737,37 +55656,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -11779,14 +55681,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11794,7 +55714,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11806,34 +55727,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -11844,18 +55778,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11863,7 +55816,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11874,34 +55828,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -11912,6 +55880,7 @@ internal static class DefaultPixelBlenders 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); @@ -11931,11 +55900,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -11943,7 +55928,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -11956,6 +55942,7 @@ internal static class DefaultPixelBlenders 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); @@ -11968,31 +55955,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -12002,6 +56000,7 @@ internal static class DefaultPixelBlenders 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, @@ -12026,10 +56025,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -12037,7 +56052,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -12049,6 +56065,7 @@ internal static class DefaultPixelBlenders 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); @@ -12062,43 +56079,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// A pixel blender that implements the "NormalDestIn" composition equation. /// - public class NormalSrcOut : PixelBlender + public class NormalDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + public static NormalDestIn Instance { get; } = new NormalDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -12118,7 +56146,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12129,7 +56157,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); } } } @@ -12145,7 +56173,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12155,14 +56183,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); } } } @@ -12188,7 +56216,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -12198,7 +56226,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); } } } @@ -12214,7 +56242,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -12223,14 +56251,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); } } } @@ -12266,7 +56294,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12278,7 +56306,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -12303,7 +56331,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12314,14 +56342,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -12361,7 +56389,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12372,7 +56400,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -12397,7 +56425,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12407,37 +56435,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -12449,14 +56460,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -12464,7 +56493,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -12476,34 +56506,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -12514,18 +56557,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -12533,7 +56595,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -12544,34 +56607,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -12582,6 +56659,7 @@ internal static class DefaultPixelBlenders 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); @@ -12601,11 +56679,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -12613,7 +56707,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -12626,6 +56721,7 @@ internal static class DefaultPixelBlenders 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); @@ -12638,31 +56734,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -12672,6 +56779,7 @@ internal static class DefaultPixelBlenders 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, @@ -12696,10 +56804,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -12707,7 +56831,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -12719,6 +56844,7 @@ internal static class DefaultPixelBlenders 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); @@ -12732,43 +56858,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddSrcOut" composition equation. + /// A pixel blender that implements the "MultiplyDestIn" composition equation. /// - public class AddSrcOut : PixelBlender + public class MultiplyDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -12788,7 +56925,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12799,7 +56936,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); } } } @@ -12815,7 +56952,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12825,14 +56962,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); } } } @@ -12858,7 +56995,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -12868,7 +57005,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); } } } @@ -12884,7 +57021,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -12893,14 +57030,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); } } } @@ -12936,7 +57073,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12948,7 +57085,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -12973,7 +57110,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -12984,14 +57121,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -13031,7 +57168,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13042,7 +57179,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -13067,7 +57204,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13077,37 +57214,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -13119,14 +57239,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13134,7 +57272,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13146,34 +57285,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -13184,18 +57336,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13203,7 +57374,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13214,34 +57386,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -13252,6 +57438,7 @@ internal static class DefaultPixelBlenders 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); @@ -13271,11 +57458,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13283,7 +57486,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13296,6 +57500,7 @@ internal static class DefaultPixelBlenders 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); @@ -13308,31 +57513,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -13342,6 +57558,7 @@ internal static class DefaultPixelBlenders 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, @@ -13366,10 +57583,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13377,7 +57610,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13389,6 +57623,7 @@ internal static class DefaultPixelBlenders 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); @@ -13402,43 +57637,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// A pixel blender that implements the "AddDestIn" composition equation. /// - public class ScreenSrcOut : PixelBlender + public class AddDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + public static AddDestIn Instance { get; } = new AddDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -13458,7 +57704,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13469,7 +57715,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); } } } @@ -13485,7 +57731,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13495,14 +57741,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); } } } @@ -13528,7 +57774,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -13538,7 +57784,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); } } } @@ -13554,7 +57800,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -13563,14 +57809,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); } } } @@ -13606,7 +57852,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13618,7 +57864,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -13643,7 +57889,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13654,14 +57900,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -13701,7 +57947,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13712,7 +57958,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -13737,7 +57983,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -13747,37 +57993,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -13789,14 +58018,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13804,7 +58051,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13816,34 +58064,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -13854,18 +58115,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13873,7 +58153,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13884,34 +58165,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -13922,6 +58217,7 @@ internal static class DefaultPixelBlenders 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); @@ -13941,11 +58237,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -13953,7 +58265,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -13966,6 +58279,7 @@ internal static class DefaultPixelBlenders 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); @@ -13978,31 +58292,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -14012,6 +58337,7 @@ internal static class DefaultPixelBlenders 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, @@ -14036,10 +58362,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -14047,7 +58389,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -14059,6 +58402,7 @@ internal static class DefaultPixelBlenders 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); @@ -14072,43 +58416,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// A pixel blender that implements the "SubtractDestIn" composition equation. /// - public class LightenSrcOut : PixelBlender + public class SubtractDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14128,7 +58483,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14139,7 +58494,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); } } } @@ -14155,7 +58510,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14165,14 +58520,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); } } } @@ -14198,7 +58553,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -14208,7 +58563,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); } } } @@ -14224,7 +58579,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -14233,14 +58588,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); } } } @@ -14276,7 +58631,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14288,7 +58643,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -14313,7 +58668,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14324,14 +58679,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -14371,7 +58726,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14382,7 +58737,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -14407,7 +58762,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14417,37 +58772,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -14459,14 +58797,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -14474,7 +58830,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -14486,34 +58843,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -14524,18 +58894,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -14543,7 +58932,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -14554,34 +58944,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -14592,6 +58996,7 @@ internal static class DefaultPixelBlenders 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); @@ -14611,11 +59016,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -14623,7 +59044,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -14636,6 +59058,7 @@ internal static class DefaultPixelBlenders 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); @@ -14648,31 +59071,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -14682,6 +59116,7 @@ internal static class DefaultPixelBlenders 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, @@ -14706,10 +59141,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -14717,7 +59168,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -14729,6 +59181,7 @@ internal static class DefaultPixelBlenders 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); @@ -14742,43 +59195,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// A pixel blender that implements the "ScreenDestIn" composition equation. /// - public class HardLightSrcOut : PixelBlender + public class ScreenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14798,7 +59262,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14809,7 +59273,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); } } } @@ -14825,7 +59289,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14835,14 +59299,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); } } } @@ -14868,7 +59332,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -14878,7 +59342,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); } } } @@ -14894,7 +59358,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -14903,14 +59367,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); } } } @@ -14946,7 +59410,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14958,7 +59422,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -14983,7 +59447,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -14994,14 +59458,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -15041,7 +59505,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15052,7 +59516,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -15077,7 +59541,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15087,37 +59551,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -15129,14 +59576,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15144,7 +59609,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15156,34 +59622,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -15194,18 +59673,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15213,7 +59711,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15224,34 +59723,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -15262,6 +59775,7 @@ internal static class DefaultPixelBlenders 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); @@ -15281,11 +59795,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15293,7 +59823,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15306,6 +59837,7 @@ internal static class DefaultPixelBlenders 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); @@ -15318,31 +59850,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -15352,6 +59895,7 @@ internal static class DefaultPixelBlenders 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, @@ -15376,10 +59920,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15387,7 +59947,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15399,6 +59960,7 @@ internal static class DefaultPixelBlenders 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); @@ -15412,43 +59974,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDest" composition equation. + /// A pixel blender that implements the "DarkenDestIn" composition equation. /// - public class MultiplyDest : PixelBlender + public class DarkenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -15468,7 +60041,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15479,7 +60052,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); } } } @@ -15495,7 +60068,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15505,14 +60078,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); } } } @@ -15538,7 +60111,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -15548,7 +60121,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); } } } @@ -15564,7 +60137,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -15573,14 +60146,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); } } } @@ -15616,7 +60189,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15628,7 +60201,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -15653,7 +60226,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15664,14 +60237,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -15711,7 +60284,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15722,7 +60295,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -15747,7 +60320,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -15757,37 +60330,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -15799,14 +60355,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15814,7 +60388,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15826,34 +60401,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -15864,18 +60452,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15883,7 +60490,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15894,34 +60502,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -15932,6 +60554,7 @@ internal static class DefaultPixelBlenders 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); @@ -15951,11 +60574,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -15963,7 +60602,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -15976,6 +60616,7 @@ internal static class DefaultPixelBlenders 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); @@ -15988,31 +60629,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -16022,6 +60674,7 @@ internal static class DefaultPixelBlenders 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, @@ -16046,10 +60699,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -16057,7 +60726,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -16069,6 +60739,7 @@ internal static class DefaultPixelBlenders 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); @@ -16082,43 +60753,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDest" composition equation. + /// A pixel blender that implements the "LightenDestIn" composition equation. /// - public class SubtractDest : PixelBlender + public class LightenDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDest Instance { get; } = new SubtractDest(); + public static LightenDestIn Instance { get; } = new LightenDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -16138,7 +60820,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16149,7 +60831,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); } } } @@ -16165,7 +60847,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16175,14 +60857,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); } } } @@ -16208,7 +60890,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -16218,7 +60900,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); } } } @@ -16234,7 +60916,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -16243,14 +60925,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); } } } @@ -16286,7 +60968,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16298,7 +60980,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -16323,7 +61005,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16334,14 +61016,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -16381,7 +61063,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16392,7 +61074,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -16417,7 +61099,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16427,37 +61109,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -16469,14 +61134,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -16484,7 +61167,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -16496,34 +61180,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -16534,18 +61231,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -16553,7 +61269,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -16564,34 +61281,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -16602,6 +61333,7 @@ internal static class DefaultPixelBlenders 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); @@ -16621,11 +61353,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -16633,7 +61381,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -16646,6 +61395,7 @@ internal static class DefaultPixelBlenders 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); @@ -16658,31 +61408,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -16692,6 +61453,7 @@ internal static class DefaultPixelBlenders 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, @@ -16716,10 +61478,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -16727,7 +61505,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -16739,6 +61518,7 @@ internal static class DefaultPixelBlenders 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); @@ -16752,43 +61532,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDest" composition equation. + /// A pixel blender that implements the "OverlayDestIn" composition equation. /// - public class DarkenDest : PixelBlender + public class OverlayDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDest Instance { get; } = new DarkenDest(); + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -16808,7 +61599,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16819,7 +61610,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); } } } @@ -16835,7 +61626,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16845,14 +61636,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); } } } @@ -16878,7 +61669,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -16888,7 +61679,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); } } } @@ -16904,7 +61695,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -16913,14 +61704,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); } } } @@ -16956,7 +61747,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -16968,7 +61759,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -16993,7 +61784,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17004,14 +61795,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -17051,7 +61842,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17062,7 +61853,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -17087,7 +61878,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17097,37 +61888,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -17139,14 +61913,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17154,7 +61946,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17166,34 +61959,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -17204,18 +62010,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17223,7 +62048,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17234,34 +62060,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -17272,6 +62112,7 @@ internal static class DefaultPixelBlenders 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); @@ -17291,11 +62132,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17303,7 +62160,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17316,6 +62174,7 @@ internal static class DefaultPixelBlenders 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); @@ -17328,31 +62187,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -17362,6 +62232,7 @@ internal static class DefaultPixelBlenders 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, @@ -17386,10 +62257,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17397,7 +62284,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17409,6 +62297,7 @@ internal static class DefaultPixelBlenders 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); @@ -17422,43 +62311,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDest" composition equation. + /// A pixel blender that implements the "HardLightDestIn" composition equation. /// - public class OverlayDest : PixelBlender + public class HardLightDestIn : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDest Instance { get; } = new OverlayDest(); + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -17478,7 +62378,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17489,7 +62389,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); } } } @@ -17505,7 +62405,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17515,14 +62415,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); } } } @@ -17548,7 +62448,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -17558,7 +62458,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); } } } @@ -17574,7 +62474,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -17583,14 +62483,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); } } } @@ -17626,7 +62526,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17638,7 +62538,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -17663,7 +62563,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17674,14 +62574,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -17721,7 +62621,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17732,7 +62632,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -17757,7 +62657,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -17767,37 +62667,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -17809,14 +62692,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17824,7 +62725,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17836,34 +62738,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -17874,18 +62789,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17893,7 +62827,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17904,34 +62839,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -17942,6 +62891,7 @@ internal static class DefaultPixelBlenders 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); @@ -17961,11 +62911,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -17973,7 +62939,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -17986,6 +62953,7 @@ internal static class DefaultPixelBlenders 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); @@ -17998,31 +62966,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -18032,6 +63011,7 @@ internal static class DefaultPixelBlenders 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, @@ -18056,10 +63036,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -18067,7 +63063,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -18079,6 +63076,7 @@ internal static class DefaultPixelBlenders 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); @@ -18092,43 +63090,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// A pixel blender that implements the "NormalDestOut" composition equation. /// - public class NormalDestAtop : PixelBlender + public class NormalDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + public static NormalDestOut Instance { get; } = new NormalDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -18148,7 +63157,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18159,7 +63168,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); } } } @@ -18175,7 +63184,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18185,14 +63194,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); } } } @@ -18218,7 +63227,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -18228,7 +63237,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); } } } @@ -18244,7 +63253,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -18253,14 +63262,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); } } } @@ -18296,7 +63305,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18308,7 +63317,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -18333,7 +63342,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18344,14 +63353,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -18391,7 +63400,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18402,7 +63411,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -18427,7 +63436,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18437,37 +63446,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -18479,14 +63471,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -18494,7 +63504,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -18506,34 +63517,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -18544,18 +63568,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -18563,7 +63606,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -18574,34 +63618,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -18612,6 +63670,7 @@ internal static class DefaultPixelBlenders 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); @@ -18631,11 +63690,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -18643,7 +63718,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -18656,6 +63732,7 @@ internal static class DefaultPixelBlenders 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); @@ -18668,31 +63745,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -18702,6 +63790,7 @@ internal static class DefaultPixelBlenders 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, @@ -18726,10 +63815,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -18737,7 +63842,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -18749,6 +63855,7 @@ internal static class DefaultPixelBlenders 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); @@ -18762,43 +63869,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddDestAtop" composition equation. + /// A pixel blender that implements the "MultiplyDestOut" composition equation. /// - public class AddDestAtop : PixelBlender + public class MultiplyDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -18818,7 +63936,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18829,7 +63947,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); } } } @@ -18845,7 +63963,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18855,14 +63973,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); } } } @@ -18888,7 +64006,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -18898,7 +64016,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); } } } @@ -18914,7 +64032,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -18923,14 +64041,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); } } } @@ -18966,7 +64084,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -18978,7 +64096,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19003,7 +64121,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19014,14 +64132,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19061,7 +64179,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19072,7 +64190,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19097,7 +64215,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19107,37 +64225,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -19149,14 +64250,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19164,7 +64283,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19176,34 +64296,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -19214,18 +64347,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19233,7 +64385,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19244,34 +64397,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -19282,6 +64449,7 @@ internal static class DefaultPixelBlenders 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); @@ -19301,11 +64469,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19313,7 +64497,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19326,6 +64511,7 @@ internal static class DefaultPixelBlenders 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); @@ -19338,31 +64524,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -19372,6 +64569,7 @@ internal static class DefaultPixelBlenders 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, @@ -19396,10 +64594,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19407,7 +64621,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19419,6 +64634,7 @@ internal static class DefaultPixelBlenders 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); @@ -19432,43 +64648,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// A pixel blender that implements the "AddDestOut" composition equation. /// - public class ScreenDestAtop : PixelBlender + public class AddDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + public static AddDestOut Instance { get; } = new AddDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -19488,7 +64715,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19499,7 +64726,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); } } } @@ -19515,7 +64742,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19525,14 +64752,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); } } } @@ -19558,7 +64785,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -19568,7 +64795,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); } } } @@ -19584,7 +64811,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -19593,14 +64820,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); } } } @@ -19636,7 +64863,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19648,7 +64875,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19673,7 +64900,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19684,14 +64911,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19731,7 +64958,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19742,7 +64969,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -19767,7 +64994,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -19777,37 +65004,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -19819,14 +65029,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19834,7 +65062,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19846,34 +65075,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -19884,18 +65126,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19903,7 +65164,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19914,34 +65176,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -19952,6 +65228,7 @@ internal static class DefaultPixelBlenders 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); @@ -19971,11 +65248,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -19983,7 +65276,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -19996,6 +65290,7 @@ internal static class DefaultPixelBlenders 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); @@ -20008,31 +65303,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -20042,6 +65348,7 @@ internal static class DefaultPixelBlenders 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, @@ -20066,10 +65373,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -20077,7 +65400,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -20089,6 +65413,7 @@ internal static class DefaultPixelBlenders 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); @@ -20102,43 +65427,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// A pixel blender that implements the "SubtractDestOut" composition equation. /// - public class LightenDestAtop : PixelBlender + public class SubtractDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -20158,7 +65494,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20169,7 +65505,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); } } } @@ -20185,7 +65521,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20195,14 +65531,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); } } } @@ -20228,7 +65564,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -20238,7 +65574,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); } } } @@ -20254,7 +65590,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -20263,14 +65599,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); } } } @@ -20306,7 +65642,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20318,7 +65654,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -20343,7 +65679,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20354,14 +65690,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -20401,7 +65737,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20412,7 +65748,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -20437,7 +65773,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20447,37 +65783,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -20489,14 +65808,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -20504,7 +65841,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -20516,34 +65854,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -20554,18 +65905,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -20573,7 +65943,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -20584,34 +65955,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -20622,6 +66007,7 @@ internal static class DefaultPixelBlenders 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); @@ -20641,11 +66027,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -20653,7 +66055,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -20666,6 +66069,7 @@ internal static class DefaultPixelBlenders 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); @@ -20678,31 +66082,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -20712,6 +66127,7 @@ internal static class DefaultPixelBlenders 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, @@ -20736,10 +66152,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -20747,7 +66179,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -20759,6 +66192,7 @@ internal static class DefaultPixelBlenders 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); @@ -20772,43 +66206,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// A pixel blender that implements the "ScreenDestOut" composition equation. /// - public class HardLightDestAtop : PixelBlender + public class ScreenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -20828,7 +66273,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20839,7 +66284,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); } } } @@ -20855,7 +66300,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20865,14 +66310,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); } } } @@ -20898,7 +66343,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -20908,7 +66353,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); } } } @@ -20924,7 +66369,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -20933,14 +66378,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); } } } @@ -20976,7 +66421,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -20988,7 +66433,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21013,7 +66458,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21024,14 +66469,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21071,7 +66516,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21082,7 +66527,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21107,7 +66552,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21117,37 +66562,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -21159,14 +66587,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21174,7 +66620,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21186,34 +66633,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -21224,18 +66684,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21243,7 +66722,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21254,34 +66734,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -21292,6 +66786,7 @@ internal static class DefaultPixelBlenders 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); @@ -21311,11 +66806,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21323,7 +66834,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21336,6 +66848,7 @@ internal static class DefaultPixelBlenders 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); @@ -21348,31 +66861,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -21382,6 +66906,7 @@ internal static class DefaultPixelBlenders 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, @@ -21406,10 +66931,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21417,7 +66958,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21429,6 +66971,7 @@ internal static class DefaultPixelBlenders 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); @@ -21442,43 +66985,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// A pixel blender that implements the "DarkenDestOut" composition equation. /// - public class MultiplyDestOver : PixelBlender + public class DarkenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -21498,7 +67052,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21509,7 +67063,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); } } } @@ -21525,7 +67079,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21535,14 +67089,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); } } } @@ -21568,7 +67122,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -21578,7 +67132,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); } } } @@ -21594,7 +67148,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -21603,14 +67157,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); } } } @@ -21646,7 +67200,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21658,7 +67212,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21683,7 +67237,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21694,14 +67248,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21741,7 +67295,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21752,7 +67306,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -21777,7 +67331,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -21787,37 +67341,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -21829,14 +67366,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21844,7 +67399,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21856,34 +67412,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -21894,18 +67463,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21913,7 +67501,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -21924,34 +67513,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -21962,6 +67565,7 @@ internal static class DefaultPixelBlenders 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); @@ -21981,11 +67585,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -21993,7 +67613,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22006,6 +67627,7 @@ internal static class DefaultPixelBlenders 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); @@ -22018,31 +67640,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -22052,6 +67685,7 @@ internal static class DefaultPixelBlenders 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, @@ -22076,10 +67710,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -22087,7 +67737,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22099,6 +67750,7 @@ internal static class DefaultPixelBlenders 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); @@ -22112,43 +67764,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// A pixel blender that implements the "LightenDestOut" composition equation. /// - public class SubtractDestOver : PixelBlender + public class LightenDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + public static LightenDestOut Instance { get; } = new LightenDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -22168,7 +67831,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22179,7 +67842,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); } } } @@ -22195,7 +67858,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22205,14 +67868,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); } } } @@ -22238,7 +67901,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -22248,7 +67911,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); } } } @@ -22264,7 +67927,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -22273,14 +67936,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); } } } @@ -22316,7 +67979,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22328,7 +67991,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -22353,7 +68016,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22364,14 +68027,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -22411,7 +68074,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22422,7 +68085,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -22447,7 +68110,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22457,37 +68120,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenDestOver" composition equation. - /// - public class ScreenDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -22499,14 +68145,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -22514,7 +68178,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22526,34 +68191,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -22564,18 +68242,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -22583,7 +68280,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22594,34 +68292,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -22632,6 +68344,7 @@ internal static class DefaultPixelBlenders 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); @@ -22651,11 +68364,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -22663,7 +68392,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22676,6 +68406,7 @@ internal static class DefaultPixelBlenders 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); @@ -22688,31 +68419,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -22722,6 +68464,7 @@ internal static class DefaultPixelBlenders 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, @@ -22746,10 +68489,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -22757,7 +68516,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -22769,6 +68529,7 @@ internal static class DefaultPixelBlenders 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); @@ -22782,43 +68543,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// A pixel blender that implements the "OverlayDestOut" composition equation. /// - public class DarkenDestOver : PixelBlender + public class OverlayDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -22838,7 +68610,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22849,7 +68621,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); } } } @@ -22865,7 +68637,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22875,14 +68647,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); } } } @@ -22908,7 +68680,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -22918,7 +68690,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); } } } @@ -22934,7 +68706,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -22943,14 +68715,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); } } } @@ -22986,7 +68758,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -22998,7 +68770,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23023,7 +68795,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23034,14 +68806,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23081,7 +68853,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23092,7 +68864,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23117,7 +68889,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23127,37 +68899,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenDestOver" composition equation. - /// - public class LightenDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -23169,14 +68924,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23184,7 +68957,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23196,34 +68970,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -23234,18 +69021,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23253,7 +69059,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23264,34 +69071,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -23302,6 +69123,7 @@ internal static class DefaultPixelBlenders 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); @@ -23321,11 +69143,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23333,7 +69171,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23346,6 +69185,7 @@ internal static class DefaultPixelBlenders 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); @@ -23358,31 +69198,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -23392,6 +69243,7 @@ internal static class DefaultPixelBlenders 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, @@ -23416,10 +69268,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23427,7 +69295,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23439,6 +69308,7 @@ internal static class DefaultPixelBlenders 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); @@ -23452,43 +69322,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// A pixel blender that implements the "HardLightDestOut" composition equation. /// - public class OverlayDestOver : PixelBlender + public class HardLightDestOut : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -23508,7 +69389,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23519,7 +69400,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); } } } @@ -23535,7 +69416,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23545,14 +69426,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); } } } @@ -23578,7 +69459,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -23588,7 +69469,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); } } } @@ -23604,7 +69485,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -23613,14 +69494,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); } } } @@ -23656,7 +69537,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23668,7 +69549,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23693,7 +69574,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23704,14 +69585,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23751,7 +69632,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23762,7 +69643,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -23787,7 +69668,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -23797,37 +69678,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightDestOver" composition equation. - /// - public class HardLightDestOver : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -23839,14 +69703,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23854,7 +69736,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23866,34 +69749,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -23904,18 +69800,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -23923,7 +69838,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -23934,34 +69850,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -23972,6 +69902,7 @@ internal static class DefaultPixelBlenders 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); @@ -23991,11 +69922,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24003,7 +69950,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24016,6 +69964,7 @@ internal static class DefaultPixelBlenders 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); @@ -24028,31 +69977,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -24062,6 +70022,7 @@ internal static class DefaultPixelBlenders 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, @@ -24086,10 +70047,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24097,7 +70074,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24109,6 +70087,7 @@ internal static class DefaultPixelBlenders 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); @@ -24122,43 +70101,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalDestIn" composition equation. + /// A pixel blender that implements the "NormalClear" composition equation. /// - public class NormalDestIn : PixelBlender + public class NormalClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); + public static NormalClear Instance { get; } = new NormalClear(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24178,7 +70168,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24189,7 +70179,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); } } } @@ -24205,7 +70195,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24215,14 +70205,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); } } } @@ -24248,7 +70238,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -24258,7 +70248,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); } } } @@ -24274,7 +70264,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -24283,14 +70273,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); } } } @@ -24326,7 +70316,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24338,7 +70328,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -24363,7 +70353,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24374,14 +70364,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -24421,7 +70411,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24432,7 +70422,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -24457,7 +70447,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24467,37 +70457,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "MultiplyDestIn" composition equation. - /// - public class MultiplyDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -24509,14 +70482,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24524,7 +70515,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24536,34 +70528,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -24574,18 +70579,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24593,7 +70617,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24604,34 +70629,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -24642,6 +70681,7 @@ internal static class DefaultPixelBlenders 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); @@ -24661,11 +70701,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24673,7 +70729,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24686,6 +70743,7 @@ internal static class DefaultPixelBlenders 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); @@ -24698,31 +70756,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -24732,6 +70801,7 @@ internal static class DefaultPixelBlenders 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, @@ -24756,10 +70826,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -24767,7 +70853,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -24779,6 +70866,7 @@ internal static class DefaultPixelBlenders 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); @@ -24792,43 +70880,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddDestIn" composition equation. + /// A pixel blender that implements the "MultiplyClear" composition equation. /// - public class AddDestIn : PixelBlender + public class MultiplyClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddDestIn Instance { get; } = new AddDestIn(); + public static MultiplyClear Instance { get; } = new MultiplyClear(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24848,7 +70947,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24859,7 +70958,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); } } } @@ -24875,7 +70974,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -24885,14 +70984,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); } } } @@ -24918,7 +71017,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -24928,7 +71027,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); } } } @@ -24944,7 +71043,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -24953,14 +71052,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); } } } @@ -24996,7 +71095,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25008,7 +71107,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25033,7 +71132,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25044,14 +71143,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25091,7 +71190,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25102,7 +71201,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25127,7 +71226,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25137,37 +71236,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractDestIn" composition equation. - /// - public class SubtractDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -25179,14 +71261,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25194,7 +71294,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25206,34 +71307,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -25244,18 +71358,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25263,7 +71396,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25274,34 +71408,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -25312,6 +71460,7 @@ internal static class DefaultPixelBlenders 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); @@ -25331,11 +71480,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25343,7 +71508,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25356,6 +71522,7 @@ internal static class DefaultPixelBlenders 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); @@ -25368,31 +71535,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -25402,6 +71580,7 @@ internal static class DefaultPixelBlenders 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, @@ -25426,10 +71605,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25437,7 +71632,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25449,6 +71645,7 @@ internal static class DefaultPixelBlenders 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); @@ -25462,43 +71659,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// A pixel blender that implements the "AddClear" composition equation. /// - public class ScreenDestIn : PixelBlender + public class AddClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + public static AddClear Instance { get; } = new AddClear(); /// 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.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -25518,7 +71726,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25529,7 +71737,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); } } } @@ -25545,7 +71753,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25555,14 +71763,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); } } } @@ -25588,7 +71796,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -25598,7 +71806,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); } } } @@ -25614,7 +71822,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -25623,14 +71831,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); } } } @@ -25666,7 +71874,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25678,7 +71886,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25703,7 +71911,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25714,14 +71922,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25761,7 +71969,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25772,7 +71980,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -25797,7 +72005,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -25807,37 +72015,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenDestIn" composition equation. - /// - public class DarkenDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -25849,14 +72040,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25864,7 +72073,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25876,34 +72086,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -25914,18 +72137,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -25933,7 +72175,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -25944,34 +72187,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -25982,6 +72239,7 @@ internal static class DefaultPixelBlenders 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); @@ -26001,11 +72259,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26013,7 +72287,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26026,6 +72301,7 @@ internal static class DefaultPixelBlenders 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); @@ -26038,31 +72314,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -26072,6 +72359,7 @@ internal static class DefaultPixelBlenders 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, @@ -26096,10 +72384,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26107,7 +72411,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26119,6 +72424,7 @@ internal static class DefaultPixelBlenders 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); @@ -26132,43 +72438,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenDestIn" composition equation. + /// A pixel blender that implements the "SubtractClear" composition equation. /// - public class LightenDestIn : PixelBlender + public class SubtractClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); + public static SubtractClear Instance { get; } = new SubtractClear(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -26188,7 +72505,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26199,7 +72516,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); } } } @@ -26215,7 +72532,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26225,14 +72542,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); } } } @@ -26258,7 +72575,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -26268,7 +72585,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); } } } @@ -26284,7 +72601,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -26293,14 +72610,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); } } } @@ -26336,7 +72653,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26348,7 +72665,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -26373,7 +72690,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26384,14 +72701,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -26431,7 +72748,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26442,7 +72759,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -26467,7 +72784,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26477,37 +72794,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlayDestIn" composition equation. - /// - public class OverlayDestIn : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -26519,14 +72819,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26534,7 +72852,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26546,34 +72865,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -26584,18 +72916,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26603,7 +72954,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26614,34 +72966,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -26652,6 +73018,7 @@ internal static class DefaultPixelBlenders 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); @@ -26671,11 +73038,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26683,7 +73066,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26696,6 +73080,7 @@ internal static class DefaultPixelBlenders 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); @@ -26708,31 +73093,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -26742,6 +73138,7 @@ internal static class DefaultPixelBlenders 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, @@ -26766,10 +73163,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -26777,7 +73190,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -26789,6 +73203,7 @@ internal static class DefaultPixelBlenders 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); @@ -26802,43 +73217,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// A pixel blender that implements the "ScreenClear" composition equation. /// - public class HardLightDestIn : PixelBlender + public class ScreenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + public static ScreenClear Instance { get; } = new ScreenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -26858,7 +73284,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26869,7 +73295,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); } } } @@ -26885,7 +73311,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -26895,14 +73321,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); } } } @@ -26928,7 +73354,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -26938,7 +73364,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); } } } @@ -26954,7 +73380,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -26963,14 +73389,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); } } } @@ -27006,7 +73432,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27018,7 +73444,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27043,7 +73469,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27054,14 +73480,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27101,7 +73527,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27112,7 +73538,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27137,7 +73563,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27147,37 +73573,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalDestOut" composition equation. - /// - public class NormalDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -27189,14 +73598,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27204,7 +73631,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27216,34 +73644,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -27254,18 +73695,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27273,7 +73733,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27284,34 +73745,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -27322,6 +73797,7 @@ internal static class DefaultPixelBlenders 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); @@ -27341,11 +73817,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27353,7 +73845,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27366,6 +73859,7 @@ internal static class DefaultPixelBlenders 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); @@ -27378,31 +73872,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -27412,6 +73917,7 @@ internal static class DefaultPixelBlenders 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, @@ -27436,10 +73942,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27447,7 +73969,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27459,6 +73982,7 @@ internal static class DefaultPixelBlenders 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); @@ -27472,43 +73996,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// A pixel blender that implements the "DarkenClear" composition equation. /// - public class MultiplyDestOut : PixelBlender + public class DarkenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + public static DarkenClear Instance { get; } = new DarkenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -27528,7 +74063,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27539,7 +74074,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); } } } @@ -27555,7 +74090,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27565,14 +74100,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); } } } @@ -27598,7 +74133,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -27608,7 +74143,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); } } } @@ -27624,7 +74159,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -27633,14 +74168,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); } } } @@ -27676,7 +74211,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27688,7 +74223,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27713,7 +74248,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27724,14 +74259,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27771,7 +74306,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27782,7 +74317,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -27807,7 +74342,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -27817,37 +74352,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "AddDestOut" composition equation. - /// - public class AddDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -27859,14 +74377,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27874,7 +74410,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27886,34 +74423,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -27924,18 +74474,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -27943,7 +74512,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -27954,34 +74524,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -27992,6 +74576,7 @@ internal static class DefaultPixelBlenders 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); @@ -28011,11 +74596,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28023,7 +74624,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28036,6 +74638,7 @@ internal static class DefaultPixelBlenders 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); @@ -28048,31 +74651,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -28082,6 +74696,7 @@ internal static class DefaultPixelBlenders 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, @@ -28106,10 +74721,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28117,7 +74748,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28129,6 +74761,7 @@ internal static class DefaultPixelBlenders 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); @@ -28142,43 +74775,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// A pixel blender that implements the "LightenClear" composition equation. /// - public class SubtractDestOut : PixelBlender + public class LightenClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + public static LightenClear Instance { get; } = new LightenClear(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28198,7 +74842,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28209,7 +74853,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); } } } @@ -28225,7 +74869,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28235,14 +74879,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); } } } @@ -28268,7 +74912,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -28278,7 +74922,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); } } } @@ -28294,7 +74938,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -28303,14 +74947,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); } } } @@ -28346,7 +74990,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28358,7 +75002,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -28383,7 +75027,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28394,14 +75038,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -28441,7 +75085,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28452,7 +75096,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -28477,7 +75121,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28487,37 +75131,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenDestOut" composition equation. - /// - public class ScreenDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -28529,14 +75156,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28544,7 +75189,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28556,34 +75202,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -28594,18 +75253,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28613,7 +75291,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28624,34 +75303,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -28662,6 +75355,7 @@ internal static class DefaultPixelBlenders 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); @@ -28681,11 +75375,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28693,7 +75403,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28706,6 +75417,7 @@ internal static class DefaultPixelBlenders 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); @@ -28718,31 +75430,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -28752,6 +75475,7 @@ internal static class DefaultPixelBlenders 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, @@ -28776,10 +75500,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -28787,7 +75527,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -28799,6 +75540,7 @@ internal static class DefaultPixelBlenders 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); @@ -28812,43 +75554,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// A pixel blender that implements the "OverlayClear" composition equation. /// - public class DarkenDestOut : PixelBlender + public class OverlayClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + public static OverlayClear Instance { get; } = new OverlayClear(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28868,7 +75621,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28879,7 +75632,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); } } } @@ -28895,7 +75648,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -28905,14 +75658,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); } } } @@ -28938,7 +75691,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -28948,7 +75701,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); } } } @@ -28964,7 +75717,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -28973,14 +75726,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); } } } @@ -29016,7 +75769,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29028,7 +75781,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29053,7 +75806,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29064,14 +75817,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29111,7 +75864,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29122,7 +75875,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29147,7 +75900,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29157,37 +75910,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenDestOut" composition equation. - /// - public class LightenDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -29199,14 +75935,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29214,7 +75968,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29226,34 +75981,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -29264,18 +76032,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29283,7 +76070,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29294,34 +76082,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -29332,6 +76134,7 @@ internal static class DefaultPixelBlenders 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); @@ -29351,11 +76154,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29363,7 +76182,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29376,6 +76196,7 @@ internal static class DefaultPixelBlenders 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); @@ -29388,31 +76209,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -29422,6 +76254,7 @@ internal static class DefaultPixelBlenders 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, @@ -29446,10 +76279,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29457,7 +76306,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29469,6 +76319,7 @@ internal static class DefaultPixelBlenders 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); @@ -29482,43 +76333,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// A pixel blender that implements the "HardLightClear" composition equation. /// - public class OverlayDestOut : PixelBlender + public class HardLightClear : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + public static HardLightClear Instance { get; } = new HardLightClear(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -29538,7 +76400,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29549,7 +76411,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); } } } @@ -29565,7 +76427,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29575,14 +76437,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); } } } @@ -29608,7 +76470,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -29618,7 +76480,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); } } } @@ -29634,7 +76496,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -29643,14 +76505,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); } } } @@ -29686,7 +76548,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29698,7 +76560,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29723,7 +76585,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29734,14 +76596,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29781,7 +76643,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29792,7 +76654,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -29817,7 +76679,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -29827,37 +76689,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightDestOut" composition equation. - /// - public class HardLightDestOut : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -29869,14 +76714,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29884,7 +76747,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29896,34 +76760,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -29934,18 +76811,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -29953,7 +76849,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -29964,34 +76861,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -30002,6 +76913,7 @@ internal static class DefaultPixelBlenders 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); @@ -30021,11 +76933,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30033,7 +76961,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30046,6 +76975,7 @@ internal static class DefaultPixelBlenders 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); @@ -30058,31 +76988,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -30092,6 +77033,7 @@ internal static class DefaultPixelBlenders 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, @@ -30116,10 +77058,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30127,7 +77085,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30139,6 +77098,7 @@ internal static class DefaultPixelBlenders 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); @@ -30152,43 +77112,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "NormalClear" composition equation. + /// A pixel blender that implements the "NormalXor" composition equation. /// - public class NormalClear : PixelBlender + public class NormalXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static NormalClear Instance { get; } = new NormalClear(); + public static NormalXor Instance { get; } = new NormalXor(); /// 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.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -30208,7 +77179,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30219,7 +77190,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); } } } @@ -30235,7 +77206,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30245,14 +77216,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); } } } @@ -30278,7 +77249,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -30288,7 +77259,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); } } } @@ -30304,7 +77275,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -30313,14 +77284,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); } } } @@ -30356,7 +77327,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30368,7 +77339,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -30393,7 +77364,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30404,14 +77375,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -30451,7 +77422,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30462,7 +77433,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -30487,7 +77458,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30497,37 +77468,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "MultiplyClear" composition equation. - /// - public class MultiplyClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -30539,14 +77493,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30554,7 +77526,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30566,34 +77539,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -30604,18 +77590,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30623,7 +77628,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30634,34 +77640,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -30672,6 +77692,7 @@ internal static class DefaultPixelBlenders 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); @@ -30691,11 +77712,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30703,7 +77740,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30716,6 +77754,7 @@ internal static class DefaultPixelBlenders 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); @@ -30728,31 +77767,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -30762,6 +77812,7 @@ internal static class DefaultPixelBlenders 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, @@ -30786,10 +77837,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -30797,7 +77864,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -30809,6 +77877,7 @@ internal static class DefaultPixelBlenders 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); @@ -30822,43 +77891,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "AddClear" composition equation. + /// A pixel blender that implements the "MultiplyXor" composition equation. /// - public class AddClear : PixelBlender + public class MultiplyXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static AddClear Instance { get; } = new AddClear(); + public static MultiplyXor Instance { get; } = new MultiplyXor(); /// 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.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -30878,7 +77958,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30889,7 +77969,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); } } } @@ -30905,7 +77985,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -30915,14 +77995,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); } } } @@ -30948,7 +78028,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -30958,7 +78038,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); } } } @@ -30974,7 +78054,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -30983,14 +78063,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); } } } @@ -31026,7 +78106,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31038,7 +78118,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31063,7 +78143,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31074,14 +78154,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31121,7 +78201,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31132,7 +78212,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31157,7 +78237,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31167,37 +78247,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "SubtractClear" composition equation. - /// - public class SubtractClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -31209,14 +78272,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31224,7 +78305,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31236,34 +78318,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -31274,18 +78369,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31293,7 +78407,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31304,34 +78419,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -31342,6 +78471,7 @@ internal static class DefaultPixelBlenders 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); @@ -31361,11 +78491,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31373,7 +78519,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31386,6 +78533,7 @@ internal static class DefaultPixelBlenders 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); @@ -31398,31 +78546,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -31432,6 +78591,7 @@ internal static class DefaultPixelBlenders 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, @@ -31456,10 +78616,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31467,7 +78643,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31479,6 +78656,7 @@ internal static class DefaultPixelBlenders 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); @@ -31492,43 +78670,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "ScreenClear" composition equation. + /// A pixel blender that implements the "AddXor" composition equation. /// - public class ScreenClear : PixelBlender + public class AddXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static ScreenClear Instance { get; } = new ScreenClear(); + public static AddXor Instance { get; } = new AddXor(); /// 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.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -31548,7 +78737,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31559,7 +78748,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); } } } @@ -31575,7 +78764,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31585,14 +78774,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); } } } @@ -31618,7 +78807,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -31628,7 +78817,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); } } } @@ -31644,7 +78833,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -31653,14 +78842,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); } } } @@ -31696,7 +78885,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31708,7 +78897,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31733,7 +78922,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31744,14 +78933,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31791,7 +78980,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31802,7 +78991,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -31827,7 +79016,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -31837,37 +79026,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -31879,14 +79051,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31894,7 +79084,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31906,34 +79097,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -31944,18 +79148,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -31963,7 +79186,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -31974,34 +79198,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -32012,6 +79250,7 @@ internal static class DefaultPixelBlenders 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); @@ -32031,11 +79270,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32043,7 +79298,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32056,6 +79312,7 @@ internal static class DefaultPixelBlenders 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); @@ -32068,31 +79325,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -32102,6 +79370,7 @@ internal static class DefaultPixelBlenders 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, @@ -32126,10 +79395,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32137,7 +79422,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32149,6 +79435,7 @@ internal static class DefaultPixelBlenders 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); @@ -32162,43 +79449,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "LightenClear" composition equation. + /// A pixel blender that implements the "SubtractXor" composition equation. /// - public class LightenClear : PixelBlender + public class SubtractXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static LightenClear Instance { get; } = new LightenClear(); + public static SubtractXor Instance { get; } = new SubtractXor(); /// 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.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -32218,7 +79516,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32229,7 +79527,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); } } } @@ -32245,7 +79543,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32255,14 +79553,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); } } } @@ -32288,7 +79586,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -32298,7 +79596,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); } } } @@ -32314,7 +79612,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -32323,14 +79621,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); } } } @@ -32366,7 +79664,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32378,7 +79676,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -32403,7 +79701,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32414,14 +79712,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -32461,7 +79759,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32472,7 +79770,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -32497,7 +79795,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32507,37 +79805,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "OverlayClear" composition equation. - /// - public class OverlayClear : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -32549,14 +79830,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32564,7 +79863,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32576,34 +79876,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -32614,18 +79927,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32633,7 +79965,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32644,34 +79977,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -32682,6 +80029,7 @@ internal static class DefaultPixelBlenders 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); @@ -32701,11 +80049,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32713,7 +80077,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32726,6 +80091,7 @@ internal static class DefaultPixelBlenders 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); @@ -32738,31 +80104,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -32772,6 +80149,7 @@ internal static class DefaultPixelBlenders 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, @@ -32796,10 +80174,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -32807,7 +80201,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -32819,6 +80214,7 @@ internal static class DefaultPixelBlenders 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); @@ -32832,43 +80228,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "HardLightClear" composition equation. + /// A pixel blender that implements the "ScreenXor" composition equation. /// - public class HardLightClear : PixelBlender + public class ScreenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static HardLightClear Instance { get; } = new HardLightClear(); + public static ScreenXor Instance { get; } = new ScreenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -32888,7 +80295,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32899,7 +80306,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); } } } @@ -32915,7 +80322,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -32925,14 +80332,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); } } } @@ -32958,7 +80365,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -32968,7 +80375,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); } } } @@ -32984,7 +80391,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -32993,14 +80400,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, amount); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); } } } @@ -33036,7 +80443,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33048,7 +80455,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33073,7 +80480,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33084,14 +80491,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33131,7 +80538,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33142,7 +80549,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33167,7 +80574,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33177,37 +80584,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "NormalXor" composition equation. - /// - public class NormalXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -33219,14 +80609,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33234,7 +80642,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33246,34 +80655,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -33284,18 +80706,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33303,7 +80744,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33314,34 +80756,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -33352,6 +80808,7 @@ internal static class DefaultPixelBlenders 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); @@ -33371,11 +80828,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33383,7 +80856,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33396,6 +80870,7 @@ internal static class DefaultPixelBlenders 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); @@ -33408,31 +80883,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -33442,6 +80928,7 @@ internal static class DefaultPixelBlenders 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, @@ -33466,10 +80953,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33477,7 +80980,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33489,6 +80993,7 @@ internal static class DefaultPixelBlenders 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); @@ -33502,43 +81007,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "MultiplyXor" composition equation. + /// A pixel blender that implements the "DarkenXor" composition equation. /// - public class MultiplyXor : PixelBlender + public class DarkenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); + public static DarkenXor Instance { get; } = new DarkenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -33558,7 +81074,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33569,7 +81085,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); } } } @@ -33585,7 +81101,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33595,14 +81111,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); } } } @@ -33628,7 +81144,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -33638,7 +81154,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); } } } @@ -33654,7 +81170,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -33663,14 +81179,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); } } } @@ -33706,7 +81222,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33718,7 +81234,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33743,7 +81259,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33754,14 +81270,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33801,7 +81317,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33812,7 +81328,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -33837,7 +81353,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -33847,37 +81363,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "AddXor" composition equation. - /// - public class AddXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -33889,14 +81388,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33904,7 +81421,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33916,34 +81434,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -33954,18 +81485,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -33973,7 +81523,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -33984,34 +81535,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -34022,6 +81587,7 @@ internal static class DefaultPixelBlenders 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); @@ -34041,11 +81607,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34053,7 +81635,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34066,6 +81649,7 @@ internal static class DefaultPixelBlenders 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); @@ -34078,31 +81662,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -34112,6 +81707,7 @@ internal static class DefaultPixelBlenders 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, @@ -34136,10 +81732,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34147,7 +81759,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34159,6 +81772,7 @@ internal static class DefaultPixelBlenders 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); @@ -34172,43 +81786,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "SubtractXor" composition equation. + /// A pixel blender that implements the "LightenXor" composition equation. /// - public class SubtractXor : PixelBlender + public class LightenXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static SubtractXor Instance { get; } = new SubtractXor(); + public static LightenXor Instance { get; } = new LightenXor(); /// 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.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -34228,7 +81853,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34239,7 +81864,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); } } } @@ -34255,7 +81880,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34265,14 +81890,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); } } } @@ -34298,7 +81923,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -34308,7 +81933,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); } } } @@ -34324,7 +81949,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -34333,14 +81958,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); } } } @@ -34376,7 +82001,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34388,7 +82013,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -34413,7 +82038,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34424,14 +82049,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -34471,7 +82096,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34482,7 +82107,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -34507,7 +82132,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34517,37 +82142,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "ScreenXor" composition equation. - /// - public class ScreenXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -34559,14 +82167,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34574,7 +82200,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34586,34 +82213,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -34624,18 +82264,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34643,7 +82302,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34654,34 +82314,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -34692,6 +82366,7 @@ internal static class DefaultPixelBlenders 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); @@ -34711,11 +82386,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34723,7 +82414,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34736,6 +82428,7 @@ internal static class DefaultPixelBlenders 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); @@ -34748,31 +82441,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -34782,6 +82486,7 @@ internal static class DefaultPixelBlenders 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, @@ -34806,10 +82511,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -34817,7 +82538,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -34829,6 +82551,7 @@ internal static class DefaultPixelBlenders 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); @@ -34842,43 +82565,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "DarkenXor" composition equation. + /// A pixel blender that implements the "OverlayXor" composition equation. /// - public class DarkenXor : PixelBlender + public class OverlayXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static DarkenXor Instance { get; } = new DarkenXor(); + public static OverlayXor Instance { get; } = new OverlayXor(); /// 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.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -34898,7 +82632,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34909,7 +82643,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); } } } @@ -34925,7 +82659,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -34935,14 +82669,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); } } } @@ -34968,7 +82702,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -34978,7 +82712,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); } } } @@ -34994,7 +82728,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -35003,14 +82737,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); } } } @@ -35046,7 +82780,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35058,7 +82792,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35083,7 +82817,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35094,14 +82828,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35141,7 +82875,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35152,7 +82886,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35177,7 +82911,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35187,37 +82921,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "LightenXor" composition equation. - /// - public class LightenXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -35229,14 +82946,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35244,7 +82979,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35256,34 +82992,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -35294,18 +83043,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35313,7 +83081,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35324,34 +83093,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -35362,6 +83145,7 @@ internal static class DefaultPixelBlenders 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); @@ -35381,11 +83165,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35393,7 +83193,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35406,6 +83207,7 @@ internal static class DefaultPixelBlenders 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); @@ -35418,31 +83220,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -35452,6 +83265,7 @@ internal static class DefaultPixelBlenders 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, @@ -35476,10 +83290,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35487,7 +83317,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35499,6 +83330,7 @@ internal static class DefaultPixelBlenders 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); @@ -35512,43 +83344,54 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } } /// - /// A pixel blender that implements the "OverlayXor" composition equation. + /// A pixel blender that implements the "HardLightXor" composition equation. /// - public class OverlayXor : PixelBlender + public class HardLightXor : PixelBlender { /// /// Gets the static instance of this blender. /// - public static OverlayXor Instance { get; } = new OverlayXor(); + public static HardLightXor Instance { get; } = new HardLightXor(); /// 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.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -35568,7 +83411,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35579,7 +83422,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } } } @@ -35595,7 +83438,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35605,14 +83448,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } } } @@ -35638,7 +83481,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -35648,7 +83491,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); } } } @@ -35664,7 +83507,7 @@ internal static class DefaultPixelBlenders while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); } @@ -35673,14 +83516,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); } } } @@ -35716,7 +83559,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35728,7 +83571,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35753,7 +83596,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35764,14 +83607,14 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35811,7 +83654,7 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35822,7 +83665,7 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } @@ -35847,7 +83690,7 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.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); @@ -35857,37 +83700,20 @@ internal static class DefaultPixelBlenders { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); } } } - } - - /// - /// A pixel blender that implements the "HardLightXor" composition equation. - /// - public class HardLightXor : PixelBlender - { - /// - /// 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.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -35899,14 +83725,32 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35914,7 +83758,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35926,34 +83771,47 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { amount = Numerics.Clamp(amount, 0, 1); @@ -35964,18 +83822,37 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -35983,7 +83860,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -35994,34 +83872,48 @@ internal static class DefaultPixelBlenders 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)) { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, amount); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -36032,6 +83924,7 @@ internal static class DefaultPixelBlenders 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); @@ -36051,11 +83944,27 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -36063,7 +83972,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -36076,6 +83986,7 @@ internal static class DefaultPixelBlenders 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); @@ -36088,31 +83999,42 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); sourceBase = ref Unsafe.Add(ref sourceBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { if (Avx512F.IsSupported && destination.Length >= 4) { @@ -36122,6 +84044,7 @@ internal static class DefaultPixelBlenders 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, @@ -36146,10 +84069,26 @@ internal static class DefaultPixelBlenders amount3, amount3, amount3, amount3); opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + float coverage0 = coverageBase; + float coverage1 = Unsafe.Add(ref coverageBase, 1); + float coverage2 = Unsafe.Add(ref coverageBase, 2); + float coverage3 = Unsafe.Add(ref coverageBase, 3); + + // We need to create a Vector512 containing the current four coverage values + // taking up each quarter of the Vector512 and then clamp them. + Vector512 coverageVector = Vector512.Create( + coverage0, coverage0, coverage0, coverage0, + coverage1, coverage1, coverage1, coverage1, + coverage2, coverage2, coverage2, coverage2, + coverage3, coverage3, coverage3, coverage3); + coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + + Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); } int remainder = Numerics.Modulo4(destination.Length); @@ -36157,7 +84096,8 @@ internal static class DefaultPixelBlenders { for (int i = destination.Length - remainder; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } @@ -36169,6 +84109,7 @@ internal static class DefaultPixelBlenders 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); @@ -36182,24 +84123,35 @@ internal static class DefaultPixelBlenders Vector128.Create(Unsafe.Add(ref amountBase, 1))); opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + // We need to create a Vector256 containing the current and next coverage values + // taking up each half of the Vector256 and then clamp them. + Vector256 coverageVector = Vector256.Create( + Vector128.Create(coverageBase), + Vector128.Create(Unsafe.Add(ref coverageBase, 1))); + coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + + Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); destinationBase = ref Unsafe.Add(ref destinationBase, 1); backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); } if (Numerics.Modulo2(destination.Length) != 0) { // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } else { for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index c2439c24c..e20e47f2b 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -401,6 +401,450 @@ var blenders = new []{ } } } + + /// + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + + /// + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 4); + coverageBase = ref Unsafe.Add(ref coverageBase, 4); + } + + int remainder = Numerics.Modulo4(destination.Length); + if (remainder != 0) + { + for (int i = destination.Length - remainder; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); + destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); + amountBase = ref Unsafe.Add(ref amountBase, 2); + coverageBase = ref Unsafe.Add(ref coverageBase, 2); + } + + if (Numerics.Modulo2(destination.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + int i = destination.Length - 1; + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + else + { + for (int i = 0; i < destination.Length; i++) + { + Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); + } + } + } } <# diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 948076fa3..b764432e8 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -324,6 +324,65 @@ internal static partial class PorterDuffFunctions return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, color)); } + /// + /// Applies raster coverage to a Porter-Duff composition result. + /// + /// The backdrop vector. + /// The Porter-Duff composition result. + /// The coverage. Range 0..1. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 backdropPremultiplied = Numerics.WithW(backdrop * backdropAlpha, backdropAlpha); + Vector4 sourcePremultiplied = Numerics.WithW(source * sourceAlpha, sourceAlpha); + Vector4 result = backdropPremultiplied + ((sourcePremultiplied - backdropPremultiplied) * coverage); + + Numerics.UnPremultiply(ref result); + return result; + } + + /// + /// Applies raster coverage to a Porter-Duff composition result. + /// + /// The backdrop vector. + /// The Porter-Duff composition result. + /// The coverage. Range 0..1. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 BlendWithCoverage(Vector256 backdrop, Vector256 source, Vector256 coverage) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + 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); + + return Numerics.UnPremultiply(result, Avx.Permute(result, ShuffleAlphaControl)); + } + + /// + /// Applies raster coverage to a Porter-Duff composition result. + /// + /// The backdrop vector. + /// The Porter-Duff composition result. + /// The coverage. Range 0..1. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 BlendWithCoverage(Vector512 backdrop, Vector512 source, Vector512 coverage) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + 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); + + return Numerics.UnPremultiply(result, Vector512_.ShuffleNative(result, ShuffleAlphaControl)); + } + /// /// Helper function for Overlay and HardLight modes /// diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index fcd28796f..0b1a51d74 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.PixelFormats; @@ -167,6 +168,162 @@ public abstract class PixelBlender PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } + /// + /// Blends 2 rows together with per-pixel coverage. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + ReadOnlySpan coverage) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); + this.BlendWithCoverage( + configuration, + destination, + background, + source, + amount, + coverage, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + ReadOnlySpan coverage, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + 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.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + } + + /// + /// Blends a row against a constant source color with per-pixel coverage. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount, + ReadOnlySpan coverage) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + this.BlendWithCoverage( + configuration, + destination, + background, + source, + amount, + coverage, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends a row against a constant source color with per-pixel coverage using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + /// Reusable temporary vector scratch with capacity for at least 2 rows. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount, + ReadOnlySpan coverage, + Span workingBuffer) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + } + /// /// Blends 2 rows together /// @@ -349,6 +506,206 @@ public abstract class PixelBlender PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); } + /// + /// Blends 2 rows together with per-pixel coverage. + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + => this.BlendWithCoverage(configuration, destination, background, source, amount, coverage); + + /// + /// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage, + Span workingBuffer) + => this.BlendWithCoverage(configuration, destination, background, source, amount, coverage, workingBuffer); + + /// + /// Blends 2 rows together with per-pixel coverage. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); + this.BlendWithCoverage( + configuration, + destination, + background, + source, + amount, + coverage, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends a row against a constant source color with per-pixel coverage. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + this.BlendWithCoverage( + configuration, + destination, + background, + source, + amount, + coverage, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends 2 rows together with per-pixel coverage using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + 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.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + } + + /// + /// Blends a row against a constant source color with per-pixel coverage using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + /// Reusable temporary vector scratch with capacity for at least 2 rows. + public void BlendWithCoverage( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount, + ReadOnlySpan coverage, + Span workingBuffer) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(coverage.Length, maxLength, nameof(coverage.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount, coverage); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale); + } + /// /// Blend 2 rows together. /// @@ -412,4 +769,108 @@ public abstract class PixelBlender ReadOnlySpan background, Vector4 source, ReadOnlySpan amount); + + /// + /// Blend 2 rows together with per-pixel coverage. + /// + /// destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + protected virtual void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + ReadOnlySpan coverage) + { + this.BlendFunction(destination, background, source, amount); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F)); + } + } + + /// + /// Blend a row against a constant source color with per-pixel coverage. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + protected virtual void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount, + ReadOnlySpan coverage) + { + this.BlendFunction(destination, background, source, amount); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F)); + } + } + + /// + /// Blend 2 rows together with per-pixel coverage. + /// + /// destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + protected virtual void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + { + this.BlendFunction(destination, background, source, amount); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F)); + } + } + + /// + /// Blend a row against a constant source color with per-pixel coverage. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// A span with coverage values between 0 and 1. + protected virtual void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + { + this.BlendFunction(destination, background, source, amount); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], destination[i], Numerics.Clamp(coverage[i], 0, 1F)); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs index a5e4e3048..bcfad0117 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Abgr32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Abgr32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index 30fed8d1c..3ef93de9f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Argb32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Argb32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index ae6be4401..6afcb6432 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Bgra32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Bgra32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index ae29223de..d53ed520b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -152,6 +152,12 @@ using SixLabors.ImageSharp.PixelFormats.Utils; { removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; } + + if (hasAlpha) + { + GenerateRgba32Compatible32BitVector4ConversionMethods(pixelType, removeTheseModifiers); + return; + } #> /// @@ -176,6 +182,45 @@ using SixLabors.ImageSharp.PixelFormats.Utils; <#+ } + void GenerateRgba32Compatible32BitVector4ConversionMethods(string pixelType, string removeTheseModifiers) + { +#> + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span<<#=pixelType#>> destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(<#=removeTheseModifiers#>)); + + Span destinationBytes = MemoryMarshal.Cast<<#=pixelType#>, byte>(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.To<#=pixelType#>(destinationBytes, destinationBytes); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan<<#=pixelType#>> source, + Span destination, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeTheseModifiers#>)); + } +<#+ + } + void GenerateAllDefaultConversionMethods(string pixelType) { GenerateDefaultSelfConversionMethods(pixelType); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index ddd79bb75..36ddad6c6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -328,6 +328,214 @@ public class PixelBlenderTests Assert.Equal(source[1], destination[1]); } + [Fact] + public void BlendWithCoverage_WithConstantSourceAndSingleAmount() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(255, 0, 0), + new(255, 0, 0) + ]; + + Rgba32 source = new(0, 0, 255); + float[] coverage = [0F, .5F, 1F]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 0, 128), destination[1]); + Assert.Equal(source, destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithConstantSourceSingleAmountAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(255, 0, 0), + new(255, 0, 0) + ]; + + Rgba32 source = new(0, 0, 255); + float[] coverage = [0F, .5F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 0, 128), destination[1]); + Assert.Equal(source, destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithSourceSpanAndSingleAmount() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(0, 255, 0), + new(0, 0, 255) + ]; + + Rgba32[] source = + [ + new(0, 0, 255), + new(255, 0, 0), + new(0, 255, 0) + ]; + + float[] coverage = [0F, .5F, 1F]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 128, 0), destination[1]); + Assert.Equal(source[2], destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithSourceSpanSingleAmountAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(0, 255, 0), + new(0, 0, 255) + ]; + + Rgba32[] source = + [ + new(0, 0, 255), + new(255, 0, 0), + new(0, 255, 0) + ]; + + float[] coverage = [0F, .5F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 3]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, workingBuffer); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 128, 0), destination[1]); + Assert.Equal(source[2], destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithConstantSourceAndAmountSpan() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(255, 0, 0), + new(255, 0, 0) + ]; + + Rgba32 source = new(0, 0, 255); + float[] amount = [1F, 1F, 1F]; + float[] coverage = [0F, .5F, 1F]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 0, 128), destination[1]); + Assert.Equal(source, destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithConstantSourceAmountSpanAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(255, 0, 0), + new(255, 0, 0) + ]; + + Rgba32 source = new(0, 0, 255); + float[] amount = [1F, 1F, 1F]; + float[] coverage = [0F, .5F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage, workingBuffer); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 0, 128), destination[1]); + Assert.Equal(source, destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithSourceSpanAndAmountSpan() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(0, 255, 0), + new(0, 0, 255) + ]; + + Rgba32[] source = + [ + new(0, 0, 255), + new(255, 0, 0), + new(0, 255, 0) + ]; + + float[] amount = [1F, 1F, 1F]; + float[] coverage = [0F, .5F, 1F]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 128, 0), destination[1]); + Assert.Equal(source[2], destination[2]); + } + + [Fact] + public void BlendWithCoverage_WithSourceSpanAmountSpanAndWorkingBuffer() + { + PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + Rgba32[] destination = new Rgba32[3]; + Rgba32[] background = + [ + new(255, 0, 0), + new(0, 255, 0), + new(0, 0, 255) + ]; + + Rgba32[] source = + [ + new(0, 0, 255), + new(255, 0, 0), + new(0, 255, 0) + ]; + + float[] amount = [1F, 1F, 1F]; + float[] coverage = [0F, .5F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 3]; + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, amount, coverage, workingBuffer); + + Assert.Equal(background[0], destination[0]); + Assert.Equal(new Rgba32(128, 128, 0), destination[1]); + Assert.Equal(source[2], destination[2]); + } + public static TheoryData ColorBlendingExpectedResults = new() { { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel() }, @@ -421,6 +629,7 @@ public class PixelBlenderTests Rgba32 background = Color.MistyRose.ToPixel(); Rgba32 source = Color.MidnightBlue.ToPixel(); float[] amount = [1F, 1F, 1F, 1F]; + float[] coverage = [1F, 1F, 1F, 1F]; Rgba32 expected = blender.Blend(background, source, 1F); @@ -441,5 +650,17 @@ public class PixelBlenderTests blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, coverage, sourceSpanBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, 1F, coverage, constantSourceBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer); + Assert.All(destination, x => Assert.Equal(expected, x)); } } From 0b685ab0348638d61a601daf97e0a254ac5f9744 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 10 Jul 2026 19:57:54 +1000 Subject: [PATCH 203/240] Fix convolution sampling for bounds smaller than the kernel radius KernelSamplingMap.CorrectBorder sliced the offset span by radius * kernelSize, so bounds narrower than the kernel radius threw ArgumentOutOfRangeException (GaussianBlur of a 130x8 region with sigma 10, for example), and bounds exactly equal to the radius let Bounce offsets escape the sampling range by folding an overshoot of a full extent to min - 1. Route bounds no larger than the radius through an exact per-mode correction that folds each offset into range: clamp for Repeat, periodic reflection for Mirror and Bounce, and true modulo for Wrap. The single-pass corrections are unchanged for larger bounds. --- .../Convolution/KernelSamplingMap.cs | 64 ++++++++++++++++++- .../Convolution/GaussianBlurTest.cs | 30 +++++++++ .../Convolution/KernelSamplingMapTests.cs | 50 +++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 7653aeaa9..2e4292890 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -104,7 +104,15 @@ internal sealed class KernelSamplingMap : IDisposable { int affectedSize = (kernelSize >> 1) * kernelSize; ref int spanBase = ref MemoryMarshal.GetReference(span); - if (affectedSize > 0) + if (affectedSize >= span.Length && span.Length > 0) + { + // The bounds are no larger than the kernel radius: every offset can overshoot the + // borders by a full sampling extent or more, which the single-pass head and tail + // corrections below cannot fold back into range (and for bounds strictly smaller + // than the radius they cannot even slice the span). Fold each offset exactly. + CorrectBorderExact(span, min, max, borderMode); + } + else if (affectedSize > 0) { switch (borderMode) { @@ -180,4 +188,58 @@ internal sealed class KernelSamplingMap : IDisposable } } } + + private static void CorrectBorderExact(Span span, int min, int max, BorderWrappingMode borderMode) + { + int extent = max - min + 1; + switch (borderMode) + { + case BorderWrappingMode.Repeat: + Numerics.Clamp(span, min, max); + break; + + case BorderWrappingMode.Mirror: + // Reflection about the half-sample border: ..., min+1, min | min, min+1, ... + int mirrorPeriod = 2 * extent; + for (int i = 0; i < span.Length; i++) + { + int offset = Modulo(span[i] - min, mirrorPeriod); + span[i] = min + (offset < extent ? offset : mirrorPeriod - 1 - offset); + } + + break; + + case BorderWrappingMode.Bounce: + // Reflection about the border sample itself: ..., min+2, min+1 | min, min+1, ... + if (extent == 1) + { + span.Fill(min); + break; + } + + int bouncePeriod = (2 * extent) - 2; + for (int i = 0; i < span.Length; i++) + { + int offset = Modulo(span[i] - min, bouncePeriod); + span[i] = min + (offset < extent ? offset : bouncePeriod - offset); + } + + break; + + case BorderWrappingMode.Wrap: + for (int i = 0; i < span.Length; i++) + { + span[i] = min + Modulo(span[i] - min, extent); + } + + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Modulo(int value, int period) + { + int remainder = value % period; + return remainder < 0 ? remainder + period : remainder; + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 0728bb5d8..f9f10cb70 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; @@ -13,4 +15,32 @@ public class GaussianBlurTest : Basic1ParameterConvolutionTests protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => ctx.GaussianBlur(bounds, value); + + [Theory] + [InlineData(BorderWrappingMode.Repeat)] + [InlineData(BorderWrappingMode.Mirror)] + [InlineData(BorderWrappingMode.Bounce)] + [InlineData(BorderWrappingMode.Wrap)] + public void OnImageSmallerThanKernelRadius_LeavesSolidColorUnchanged(BorderWrappingMode mode) + { + // 8 rows against sigma 10 (61-tap kernel, radius 30). A normalized blur of a solid + // image must remain that color no matter how the border sampling folds. + Rgba32 red = new(255, 0, 0); + using Image image = new(130, 8, red); + image.Mutate(x => x.GaussianBlur(image.Bounds, 10F, mode, mode)); + + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + foreach (Rgba32 pixel in accessor.GetRowSpan(y)) + { + Assert.InRange(pixel.R, 254, 255); + Assert.InRange(pixel.G, 0, 1); + Assert.InRange(pixel.B, 0, 1); + Assert.Equal(255, pixel.A); + } + } + }); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs new file mode 100644 index 000000000..5194fa001 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class KernelSamplingMapTests +{ + public static readonly TheoryData BoundsSmallerThanKernel = new() + { + { BorderWrappingMode.Repeat, 8, 5 }, + { BorderWrappingMode.Mirror, 8, 5 }, + { BorderWrappingMode.Bounce, 8, 5 }, + { BorderWrappingMode.Wrap, 8, 5 }, + { BorderWrappingMode.Repeat, 1, 1 }, + { BorderWrappingMode.Mirror, 1, 1 }, + { BorderWrappingMode.Bounce, 1, 1 }, + { BorderWrappingMode.Wrap, 1, 1 }, + { BorderWrappingMode.Repeat, 30, 30 }, + { BorderWrappingMode.Mirror, 30, 30 }, + { BorderWrappingMode.Bounce, 30, 30 }, + { BorderWrappingMode.Wrap, 30, 30 }, + }; + + [Theory] + [MemberData(nameof(BoundsSmallerThanKernel))] + public void BuildSamplingOffsetMap_BoundsSmallerThanKernelRadius_OffsetsStayInBounds(BorderWrappingMode mode, int width, int height) + { + // A 61-tap kernel has radius 30, so these bounds are covered entirely by the border + // regions and offsets can overshoot the bounds by more than one sampling extent. + const int kernelSize = 61; + Rectangle bounds = new(100, 200, width, height); + + using KernelSamplingMap map = new(Configuration.Default.MemoryAllocator); + map.BuildSamplingOffsetMap(kernelSize, kernelSize, bounds, mode, mode); + + foreach (int x in map.GetColumnOffsetSpan()) + { + Assert.InRange(x, bounds.Left, bounds.Right - 1); + } + + foreach (int y in map.GetRowOffsetSpan()) + { + Assert.InRange(y, bounds.Top, bounds.Bottom - 1); + } + } +} From c4683902a4aa1187e3cd2176c15306a139e2da39 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Jul 2026 07:47:16 +1000 Subject: [PATCH 204/240] Add associated-alpha pixel formats Introduce Rgba32P, Bgra32P, Argb32P, Abgr32P, NormalizedByte4P, and HalfVector4P. Add representation-aware scalar and bulk conversions, optimized pixel operations, and associated-alpha blending across all Porter-Duff modes. Include comprehensive conversion, layout, blending, and performance coverage. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 12 + src/ImageSharp/Color/Color.cs | 139 +- .../Common/Helpers/SimdUtils.Convert.cs | 32 +- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 60 +- .../Common/Helpers/Vector128Utilities.cs | 25 + .../Common/Helpers/Vector256Utilities.cs | 20 + .../Common/Helpers/Vector512Utilities.cs | 13 + src/ImageSharp/ImageSharp.csproj | 18 + .../AssociatedAlphaPixelOperations{TPixel}.cs | 258 + .../AssociatedAlphaPixelBlenders.Generated.cs | 84152 ++++++++++++++++ .../AssociatedAlphaPixelBlenders.Generated.tt | 847 + .../AssociatedAlphaPixelBlenders.cs | 168 + .../AssociatedAlphaPixelBlender{TPixel}.cs | 46 + ...iatedAlphaPorterDuffFunctions.Generated.cs | 5035 + ...iatedAlphaPorterDuffFunctions.Generated.tt | 138 + .../AssociatedAlphaPorterDuffFunctions.cs | 546 + .../PixelBlenders/PorterDuffFunctions.cs | 4 +- .../PixelFormats/PixelBlender{TPixel}.cs | 82 +- .../PixelImplementations/Abgr32P.cs | 241 + .../PixelImplementations/Argb32P.cs | 241 + .../PixelImplementations/Bgra32P.cs | 241 + .../PixelImplementations/HalfVector4P.cs | 243 + .../PixelImplementations/NormalizedByte4P.cs | 276 + .../Abgr32P.PixelOperations.cs | 66 + .../Argb32P.PixelOperations.cs | 66 + .../Bgra32P.PixelOperations.cs | 66 + .../Bgr24.PixelOperations.Generated.cs | 3 +- .../Rgb24.PixelOperations.Generated.cs | 3 +- .../Generated/_Common.ttinclude | 14 +- .../HalfVector4P.PixelOperations.cs | 57 + .../NormalizedByte4P.PixelOperations.cs | 62 + .../Rgba32P.PixelOperations.cs | 66 + .../RgbaVector.PixelOperations.cs | 5 +- .../PixelImplementations/Rgba32P.cs | 241 + .../PixelFormats/PixelOperations{TPixel}.cs | 65 +- ...tor4Converters.AssociatedRgbaCompatible.cs | 595 + .../Utils/Vector4Converters.RgbaCompatible.cs | 11 +- .../ImageSharp.Benchmarks/Bulk/FromVector4.cs | 79 +- .../Bulk/ToVector4_Bgra32.cs | 64 +- tests/ImageSharp.Benchmarks/Config.cs | 29 + .../AssociatedAlphaPixelBlenderBenchmark.cs | 101 + .../Color/ColorTests.CastFrom.cs | 14 + .../Color/ColorTests.CastTo.cs | 59 + tests/ImageSharp.Tests/Color/ColorTests.cs | 22 + .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 43 +- .../PixelFormats/AssociatedAlphaPixelTests.cs | 667 + .../PixelFormats/PixelBlenderTests.cs | 173 +- ...ConverterTests.ReferenceImplementations.cs | 8 +- ...elOperationsTests.Specialized.Generated.cs | 93 +- .../Generated/_Common.ttinclude | 16 +- .../PixelOperations/PixelOperationsTests.cs | 135 +- ...GifDecoder_Decode_Resize_giphy_150_150.png | 4 +- ...ngDecoder_Decode_Resize_splash_150_150.png | 4 +- ...der_Decode_Resize_rgb_a_rle_UL_150_150.png | 4 +- ..._JpegCompressedWithIssue2679_Issue2679.png | 4 +- ...size_RgbaUnassociatedAlpha3bit_150_150.png | 4 +- ...er_Decode_Resize_bike_lossless_150_150.png | 4 +- 57 files changed, 95465 insertions(+), 219 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs create mode 100644 src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs create mode 100644 tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 0f28b2890..ce41e3a0d 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -81,10 +81,13 @@ internal static class AotCompilerTools Seed(); Seed(); + Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -95,8 +98,10 @@ internal static class AotCompilerTools Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -104,6 +109,7 @@ internal static class AotCompilerTools Seed(); Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -158,10 +164,13 @@ internal static class AotCompilerTools 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,8 +181,10 @@ internal static class AotCompilerTools 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); @@ -181,6 +192,7 @@ internal static class AotCompilerTools img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index dd248d488..7969f4728 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -20,30 +20,67 @@ namespace SixLabors.ImageSharp; public readonly partial struct Color : IEquatable { private readonly Vector4 data; + private readonly Vector4 unassociatedData; private readonly IPixel? boxedHighPrecisionPixel; + private readonly bool isAssociated; /// /// Initializes a new instance of the struct. /// /// The containing the color information. + /// The alpha representation of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(Vector4 vector) + private Color(Vector4 vector, PixelAlphaRepresentation alphaRepresentation) + { + Vector4 clamped = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + Vector4 unassociated = clamped; + + if (alphaRepresentation == PixelAlphaRepresentation.Associated) + { + Numerics.UnPremultiply(ref unassociated); + } + + this.data = clamped; + this.unassociatedData = unassociated; + this.boxedHighPrecisionPixel = null; + this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + /// The unassociated representation of . + /// The alpha representation of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Color(Vector4 vector, Vector4 unassociatedVector, PixelAlphaRepresentation alphaRepresentation) { this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.unassociatedData = Numerics.Clamp(unassociatedVector, Vector4.Zero, Vector4.One); this.boxedHighPrecisionPixel = null; + this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; } /// /// 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.unassociatedData = default; + this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; } + /// + /// 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 +117,36 @@ public readonly partial struct Color : IEquatable { // Avoid boxing in case we can convert to Vector4 safely and efficiently PixelTypeInfo info = TPixel.GetPixelTypeInfo(); + if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) { - return new Color(source.ToScaledVector4()); + Vector4 vector = source.ToScaledVector4(); + Vector4 unassociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated + ? PixelOperations.Instance.ToUnassociatedScaledVector4(source) + : vector; + + return new Color(vector, unassociated, 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); + + /// + /// 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); /// /// Bulk converts a span of generic scaled to a span of . @@ -103,11 +155,23 @@ public readonly partial struct Color : IEquatable /// 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,18 +189,23 @@ public readonly partial struct Color : IEquatable // Avoid boxing in case we can convert to Vector4 safely and efficiently PixelTypeInfo info = TPixel.GetPixelTypeInfo(); + if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) { + bool isAssociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated; + for (int i = 0; i < source.Length; i++) { - destination[i] = FromScaledVector(source[i].ToScaledVector4()); + Vector4 vector = source[i].ToScaledVector4(); + Vector4 unassociated = isAssociated ? PixelOperations.Instance.ToUnassociatedScaledVector4(source[i]) : vector; + destination[i] = new Color(vector, unassociated, info.AlphaRepresentation); } } else { for (int i = 0; i < source.Length; i++) { - destination[i] = new Color(source[i]); + destination[i] = new Color(source[i], info.AlphaRepresentation); } } } @@ -276,13 +345,30 @@ public readonly partial struct Color : IEquatable /// 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); + float clampedAlpha = Numerics.Clamp(alpha, 0, 1); + + if (this.isAssociated) + { + Vector4 unassociated = this.boxedHighPrecisionPixel is null ? this.unassociatedData : v; + + if (this.boxedHighPrecisionPixel is not null) + { + Numerics.UnPremultiply(ref unassociated); + } + + unassociated.W = clampedAlpha; + Vector4 associated = unassociated; + Numerics.Premultiply(ref associated); + return new Color(associated, unassociated, PixelAlphaRepresentation.Associated); + } + + v.W = clampedAlpha; + return FromScaledVector(v, PixelAlphaRepresentation.Unassociated); } /// @@ -296,9 +382,7 @@ public readonly partial struct Color : IEquatable [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,17 +411,23 @@ public readonly partial struct Color : IEquatable return pixel; } - if (this.boxedHighPrecisionPixel is null) + Vector4 vector = this.boxedHighPrecisionPixel is null + ? this.isAssociated ? this.unassociatedData : this.data + : this.boxedHighPrecisionPixel.ToScaledVector4(); + + if (this.isAssociated && this.boxedHighPrecisionPixel is not null) { - return TPixel.FromScaledVector4(this.data); + Numerics.UnPremultiply(ref vector); } - return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + // Destination pixel operations own association because RGB must use the alpha value representable by that destination format. + return PixelOperations.Instance.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 . @@ -377,10 +467,11 @@ public readonly partial struct Color : IEquatable { if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) { - return this.data == other.data; + return this.data == other.data && this.isAssociated == other.isAssociated; } - return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; + return this.isAssociated == other.isAssociated + && this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; } /// @@ -392,10 +483,10 @@ public readonly partial struct Color : IEquatable { if (this.boxedHighPrecisionPixel is null) { - return this.data.GetHashCode(); + return HashCode.Combine(this.data, this.isAssociated); } - return this.boxedHighPrecisionPixel.GetHashCode(); + return HashCode.Combine(this.boxedHighPrecisionPixel.ToScaledVector4(), this.isAssociated); } /// diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs index 5318ad049..2efa3f545 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs @@ -41,38 +41,58 @@ internal static partial class SimdUtils { DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!"); - HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref destination); + HwIntrinsics.FloatToByteSaturateReduce(ref source, ref destination, byte.MaxValue); if (source.Length > 0) { - ConvertNormalizedFloatToByteRemainder(source, destination); + ConvertFloatToByteRemainder(source, destination, byte.MaxValue); + } + } + + /// + /// 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); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span destination) { + const float scale = 1F / byte.MaxValue; ref byte sBase = ref MemoryMarshal.GetReference(source); ref float dBase = ref MemoryMarshal.GetReference(destination); for (int i = 0; i < source.Length; i++) { - Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) / 255f; + // Match the SIMD conversion so one span cannot contain different float representations of the same byte value. + Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) * scale; } } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan 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 022056deb..cb172d145 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -834,6 +834,19 @@ internal static partial class SimdUtils 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 +871,10 @@ internal static partial class SimdUtils if (adjustedCount > 0) { - NormalizedFloatToByteSaturate( + FloatToByteSaturate( source[..adjustedCount], - destination[..adjustedCount]); + destination[..adjustedCount], + scaleFactor); source = source[adjustedCount..]; destination = destination[adjustedCount..]; @@ -880,6 +894,18 @@ internal static partial class SimdUtils internal static void NormalizedFloatToByteSaturate( ReadOnlySpan 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 +916,7 @@ internal static partial class SimdUtils 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 +928,10 @@ internal static partial class SimdUtils 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 +950,7 @@ internal static partial class SimdUtils 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 +962,10 @@ internal static partial class SimdUtils 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 +985,7 @@ internal static partial class SimdUtils 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 +998,10 @@ internal static partial class SimdUtils 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 a5d377eb9..7ea77fadf 100644 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs @@ -307,6 +307,31 @@ internal static class Vector128_ 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. diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs index 90e3169b3..caef699dd 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -73,6 +73,26 @@ internal static class Vector256_ 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. diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs index 82a20158a..17f9e9c51 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -59,6 +59,19 @@ internal static class Vector512_ public static 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. diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 32a5f0073..9f545fc2a 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 000000000..2c84bcfc8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs @@ -0,0 +1,258 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides bulk operations for pixel formats that store associated alpha. +/// +/// The associated-alpha pixel format. +internal class AssociatedAlphaPixelOperations : PixelOperations + where TPixel : unmanaged, IPixel +{ + /// + public override PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + => AssociatedAlphaPixelBlenders.GetPixelBlender(colorMode, alphaMode); + + /// + internal override Vector4 ToUnassociatedScaledVector4(TPixel source) + { + Vector4 vector = source.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + internal override TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Associate(source)); + + /// + /// Converts an associated scaled vector to a destination pixel after associating RGB with the alpha value the destination stores. + /// + /// The associated scaled vector. + /// The destination pixel. + public virtual TPixel FromAssociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Reassociate(source)); + + /// + internal override void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + Numerics.UnPremultiply(destination[..source.Length]); + } + + /// + internal override void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + + /// + internal override void FromUnassociatedScaledVector4( + Configuration configuration, + Span source, + Span destination) + { + source = source[..destination.Length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = Associate(source[i]); + } + + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + /// Converts associated scaled vectors to destination pixels after associating RGB with the alpha values the destination stores. + /// + /// The configuration. + /// The associated scaled vectors. + /// The destination pixels. + public virtual void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = Reassociate(source[i]); + } + + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void From( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + const int sliceLength = 1024; + int numberOfSlices = source.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan(); + + // Convert through unassociated vectors so the destination operation can quantize alpha to its own storage before associating RGB. + for (int i = 0; i < numberOfSlices; i++) + { + int start = i * sliceLength; + ReadOnlySpan sourceSlice = source.Slice(start, sliceLength); + Span destinationSlice = destination.Slice(start, sliceLength); + PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); + } + + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; + + if (remainder > 0) + { + ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; + Span destinationSlice = destination[endOfCompleteSlices..]; + vectorSpan = vectorSpan[..remainder]; + PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); + } + } + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + => base.FromVector4Destructive(configuration, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + => base.ToVector4(configuration, source, destinationVectors, modifiers.Remove(PixelConversionModifiers.Premultiply)); + + /// + 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)); + + ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); + ref TDestinationPixel destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Vector4 vector = this.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i)); + Unsafe.Add(ref destinationBase, i) = TDestinationPixel.FromScaledVector4(vector); + } + } + + /// + /// Converts an unassociated scaled vector to the associated representation of the destination pixel type. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Associate(Vector4 source) + { + // Round-trip alpha through TPixel so the generic fallback associates RGB with the value the destination actually stores. + source.W = TPixel.FromScaledVector4(new Vector4(0, 0, 0, source.W)).ToScaledVector4().W; + Numerics.Premultiply(ref source); + return source; + } + + /// + /// Reassociates a scaled vector with the alpha value the destination pixel can store. + /// + /// 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 = TPixel.FromScaledVector4(new Vector4(0, 0, 0, alpha)).ToScaledVector4().W; + + // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. + source *= storedAlpha / alpha; + source.W = storedAlpha; + return source; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs new file mode 100644 index 000000000..49c1e4709 --- /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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), 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 000000000..b18f94a48 --- /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 FromBlendVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), 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 000000000..6ce1235b4 --- /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 000000000..64d0be696 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// 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 +{ + // Associated blenders are created only by AssociatedAlphaPixelOperations, so the cached cast establishes the format-specific output boundary once per closed pixel type. + private static readonly AssociatedAlphaPixelOperations Operations = (AssociatedAlphaPixelOperations)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.ToAssociatedScaledVector4(configuration, source, destination); + } + + /// + protected override Vector4 ToBlendVector4(TPixel source) => source.ToScaledVector4(); + + /// + /// Converts an associated blend result to the destination pixel representation. + /// + /// The associated blend result. + /// The destination pixel. + public static TPixel FromBlendVector4(Vector4 source) => Operations.FromAssociatedScaledVector4(source); + + /// + protected override void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + { + Operations.FromAssociatedScaledVector4(configuration, source, destination); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs new file mode 100644 index 000000000..cd5a37a11 --- /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 000000000..e0652d679 --- /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 000000000..4f6af32c8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs @@ -0,0 +1,546 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// 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 result = (destination * (Vector4.One - sourceAlpha)) + overlap; + 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 = (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 = (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_.MultiplyAdd(backdrop.AsVector128(), (source - backdrop).AsVector128(), Vector128.Create(coverage)).AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 BlendWithCoverage(Vector256 backdrop, Vector256 source, Vector256 coverage) + => Vector256_.MultiplyAdd(backdrop, source - backdrop, coverage); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 BlendWithCoverage(Vector512 backdrop, Vector512 source, Vector512 coverage) + => Vector512_.MultiplyAdd(backdrop, source - backdrop, coverage); + + /// + /// 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/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index b764432e8..1e5fc8df9 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -338,7 +338,9 @@ internal static partial class PorterDuffFunctions Vector4 sourceAlpha = Numerics.PermuteW(source); Vector4 backdropPremultiplied = Numerics.WithW(backdrop * backdropAlpha, backdropAlpha); Vector4 sourcePremultiplied = Numerics.WithW(source * sourceAlpha, sourceAlpha); - Vector4 result = backdropPremultiplied + ((sourcePremultiplied - backdropPremultiplied) * coverage); + + // Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths. + Vector4 result = Vector128_.MultiplyAdd(backdropPremultiplied.AsVector128(), (sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage)).AsVector4(); Numerics.UnPremultiply(ref result); return result; diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 0b1a51d74..47a18f54c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -93,12 +93,12 @@ public abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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 abstract class PixelBlender 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.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + /// 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) + => PixelOperations.Instance.ToUnassociatedScaledVector4(source); + + /// + /// 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.FromUnassociatedScaledVector4(configuration, source, destination); + /// /// Blend 2 rows together. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs new file mode 100644 index 000000000..780dbd2f8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// 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. +/// +/// +/// Component, packed, and vector values use associated alpha representation. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Abgr32P : IPixel, IPackedVector +{ + private const float ByteScale = 1F / byte.MaxValue; + + 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) * ByteScale; + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + 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) => Pack(source); + + /// + public static Abgr32P FromVector4(Vector4 source) => FromScaledVector4(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})"; + + /// + /// Converts an unassociated scaled vector to associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static Abgr32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Abgr32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + Vector128 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/Argb32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs new file mode 100644 index 000000000..fac4f7701 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// 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. +/// +/// +/// Component, packed, and vector values use associated alpha representation. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Argb32P : IPixel, IPackedVector +{ + private const float ByteScale = 1F / byte.MaxValue; + + 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) * ByteScale; + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + 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) => Pack(source); + + /// + public static Argb32P FromVector4(Vector4 source) => FromScaledVector4(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})"; + + /// + /// Converts an unassociated scaled vector to associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static Argb32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Argb32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + Vector128 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/Bgra32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs new file mode 100644 index 000000000..a69dfeb46 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// 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. +/// +/// +/// Component, packed, and vector values use associated alpha representation. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Bgra32P : IPixel, IPackedVector +{ + private const float ByteScale = 1F / byte.MaxValue; + + 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) * ByteScale; + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + 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) => Pack(source); + + /// + public static Bgra32P FromVector4(Vector4 source) => FromScaledVector4(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})"; + + /// + /// Converts an unassociated scaled vector to associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static Bgra32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Bgra32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + Vector128 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/HalfVector4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs new file mode 100644 index 000000000..7b3bda330 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs @@ -0,0 +1,243 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 16-bit floating-point values. +/// +/// +/// Packed and vector values use associated alpha representation. +/// +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() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return Rgba32.FromScaledVector4(vector); + } + + /// + public readonly Vector4 ToScaledVector4() + { + Vector4 scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + 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))); + + /// + 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) + { + source *= 2F; + source -= Vector4.One; + return FromVector4(source); + } + + /// + public static HalfVector4P FromVector4(Vector4 source) => new() { PackedValue = Pack(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 associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static HalfVector4P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Associate(source)); + + /// + /// 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) + { + // Quantize alpha through the destination's native half representation before RGB is associated with it. + float nativeAlpha = (source.W * 2F) - 1F; + source.W = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; + Numerics.Premultiply(ref source); + return source; + } + + /// + /// Converts unassociated scaled vectors to the associated representation of a half-precision destination. + /// + /// The vectors to convert in place. + private static void Associate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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 = (alpha * 2F) - 1F; + float storedAlpha = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; + + // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. + source *= storedAlpha / alpha; + source.W = storedAlpha; + return source; + } + + /// + /// Reassociates scaled vectors with the alpha values the destination stores. + /// + /// The vectors to convert in place. + private static void Reassociate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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/NormalizedByte4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs new file mode 100644 index 000000000..6a3182777 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -0,0 +1,276 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1. +/// +/// +/// Packed and vector values use associated alpha representation. +/// +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 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() + { + Vector4 scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + 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 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) + { + source *= 2F; + source -= Vector4.One; + return FromVector4(source); + } + + /// + public static NormalizedByte4P FromVector4(Vector4 source) => 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 associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) + { + return FromScaledVector4(Associate(source)); + } + + /// + /// 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) + { + // 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) + MaxPos, + (sbyte)(source.PackedValue >> 8) + MaxPos, + (sbyte)(source.PackedValue >> 16) + MaxPos, + (sbyte)(source.PackedValue >> 24) + MaxPos); + + if (vector.W == 0F) + { + // Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case. + return vector / ScaledMagnitude; + } + + Numerics.UnPremultiply(ref vector); + vector.W /= ScaledMagnitude; + return vector; + } + + /// + /// Converts unassociated scaled vectors to the associated representation of a signed-normalized-byte destination. + /// + /// The vectors to convert in place. + private static void Associate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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; + return source; + } + + /// + /// Reassociates scaled vectors with the alpha values the destination stores. + /// + /// The vectors to convert in place. + private static void Reassociate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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/PixelOperations/Abgr32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs new file mode 100644 index 000000000..0888130f7 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(Abgr32P source) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + + /// + internal override Abgr32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + /// + public override Abgr32P FromAssociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + } +} 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 000000000..472f22dc6 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(Argb32P source) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + + /// + internal override Argb32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + /// + public override Argb32P FromAssociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + } +} 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 000000000..f81493469 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(Bgra32P source) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + + /// + internal override Bgra32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + /// + public override Bgra32P FromAssociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + } +} 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 e3ff3a7fe..773b8c1b8 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.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public partial struct Bgr24 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 fd6465a22..5c8aeea76 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.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public partial struct Rgb24 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 d53ed520b..abf077d15 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/HalfVector4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs new file mode 100644 index 000000000..39e948fe1 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides bulk operations. +/// +public partial struct HalfVector4P +{ + /// + /// Provides bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(HalfVector4P source) + { + Vector4 vector = source.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + internal override HalfVector4P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Associate(source)); + + /// + public override HalfVector4P FromAssociatedScaledVector4(Vector4 source) + => FromScaledVector4(Reassociate(source)); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + Numerics.UnPremultiply(destination[..source.Length]); + } + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Reassociate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + } +} 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 000000000..e8a9b875a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides bulk operations. +/// +public partial struct NormalizedByte4P +{ + /// + /// Provides bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source) + => NormalizedByte4P.ToUnassociatedScaledVector4(source); + + /// + internal override NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Associate(source)); + + /// + public override NormalizedByte4P FromAssociatedScaledVector4(Vector4 source) + => FromScaledVector4(Reassociate(source)); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ref NormalizedByte4P sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref destinationBase, i) = NormalizedByte4P.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Reassociate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + } +} 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 000000000..7f7386f1d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + internal override Vector4 ToUnassociatedScaledVector4(Rgba32P source) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + + /// + internal override Rgba32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + /// + public override Rgba32P FromAssociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source); + + /// + internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.Associate(source); + this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + } + + /// + public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + { + source = source[..destination.Length]; + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index 5f9b51af9..077164da7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -25,7 +25,10 @@ public partial struct RgbaVector { Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourcePixels, destinationVectors); + + // RgbaVector.FromScaledVector4 clamps scaled input, so the optimized bulk path must preserve that behavior after unassociating. + Numerics.Clamp(MemoryMarshal.Cast(destinationVectors), 0F, 1F); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs new file mode 100644 index 000000000..46b6a4f65 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// 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. +/// +/// +/// Component, packed, and vector values use associated alpha representation. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba32P : IPixel, IPackedVector +{ + private const float ByteScale = 1F / byte.MaxValue; + + 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) * ByteScale; + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + 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) => Pack(source); + + /// + public static Rgba32P FromVector4(Vector4 source) => FromScaledVector4(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})"; + + /// + /// Converts an unassociated scaled vector to associated representation. + /// + /// The unassociated scaled vector. + /// The associated pixel. + private static Rgba32P FromUnassociatedScaledVector4(Vector4 source) + => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Rgba32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + Vector128 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/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index ea970a718..08a3044b7 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -26,6 +26,59 @@ public partial class PixelOperations public static PixelOperations Instance => LazyInstance.Value; #pragma warning restore CA1000 // Do not declare static members on generic types + /// + /// Converts a pixel to the unassociated scaled vector representation used by color operations. + /// + /// The source pixel. + /// The unassociated scaled vector. + internal virtual Vector4 ToUnassociatedScaledVector4(TPixel source) => source.ToScaledVector4(); + + /// + /// Converts an unassociated scaled vector to a pixel. + /// + /// The unassociated scaled vector. + /// The converted pixel. + internal virtual TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(source); + + /// + /// Converts pixels to the unassociated scaled vector representation used by color operations. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + internal virtual void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + + /// + /// Converts pixels to associated scaled vectors. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + internal virtual void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + Numerics.Premultiply(destination[..source.Length]); + } + + /// + /// Converts unassociated scaled vectors to pixels. + /// + /// The configuration. + /// The source vectors. + /// The destination pixels. + internal virtual void FromUnassociatedScaledVector4( + Configuration configuration, + Span source, + Span destination) + => this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + /// /// Gets the pixel type info for the given . /// @@ -121,12 +174,13 @@ public partial class PixelOperations 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); + PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan); this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } @@ -137,7 +191,7 @@ public partial class PixelOperations ReadOnlySpan s = source[endOfCompleteSlices..]; Span d = destination[endOfCompleteSlices..]; vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan); this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } } @@ -159,6 +213,13 @@ public partial class PixelOperations 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/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs new file mode 100644 index 000000000..7ab839cc5 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -0,0 +1,595 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// 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); + + if (alpha == 0) + { + // Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case. + return vector / byte.MaxValue; + } + + Numerics.UnPremultiply(ref vector); + vector.W /= byte.MaxValue; + return vector; + } + + /// + /// Converts an unassociated scaled vector to the associated representation of an unsigned-byte destination. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 Associate(Vector4 source) + { + float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue); + + // Associate in byte magnitude before normalization. Multiplying by a normalized alpha and later restoring byte magnitude can move an exact half-byte across its rounding boundary. + source *= storedAlpha; + source.W = storedAlpha; + return source / byte.MaxValue; + } + + /// + /// Converts unassociated scaled vectors to the associated representation of an unsigned-byte destination. + /// + /// The vectors to convert in place. + internal static void Associate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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; + source.W = storedAlpha; + return source; + } + + // Recover straight RGB before multiplying by the stored alpha byte. Keeping the association in byte magnitude preserves exact half-byte rounding boundaries. + Numerics.UnPremultiply(ref source); + source *= storedAlpha; + source.W = storedAlpha; + return source; + } + + /// + /// 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 (Avx512F.IsSupported) + { + ref Vector512 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 4u); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + source = source[(source.Length & ~3)..]; + } + else if (Avx.IsSupported) + { + ref Vector256 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / 2u); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + source = source[(source.Length & ~1)..]; + } + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = ReassociateToByte(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// 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_.ShuffleNative(source, 0b_11_11_11_11); + 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); + 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 = Avx.Permute(source, 0b_11_11_11_11); + Vector256 byteAlpha = alpha * byteMax; + Vector256 storedAlpha = Avx.Floor(Avx.Min(Avx.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 = Avx.BlendVariable(result, source * byteMax, Avx.CompareEqual(byteAlpha, storedAlpha)); + result = Avx.Blend(result, storedAlpha, 0b_1000_1000); + return Avx.BlendVariable(result, zero, Avx.CompareEqual(alpha, zero)); + } + + /// + /// 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 premultiplied RGBA pixels to vectors. + /// + /// The source pixels. + /// The destination vectors. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToVector4( + ReadOnlySpan source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + } + + /// + /// 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)); + + ref Rgba32P sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Rgba32P pixel = Unsafe.Add(ref sourceBase, i); + Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + } + } + + /// + /// Converts vectors to premultiplied RGBA pixels. + /// + /// The source vectors. + /// The destination pixels. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void FromVector4( + Span source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts associated scaled vectors to premultiplied RGBA pixels. + /// + /// The source vectors. + /// 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 premultiplied BGRA pixels to vectors. + /// + /// The source pixels. + /// The destination vectors. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToVector4( + ReadOnlySpan source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed BGRA bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span 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(); + ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + } + + /// + /// 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)); + + ref Bgra32P sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Bgra32P pixel = Unsafe.Add(ref sourceBase, i); + Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + } + } + + /// + /// Converts vectors to premultiplied BGRA pixels. + /// + /// The source vectors. + /// The destination pixels. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void FromVector4( + Span source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + + // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied BGRA pixels. + /// + /// The source vectors. + /// 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 premultiplied ARGB pixels to vectors. + /// + /// The source pixels. + /// The destination vectors. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToVector4( + ReadOnlySpan source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed ARGB bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span 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(); + ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + } + + /// + /// 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)); + + ref Argb32P sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Argb32P pixel = Unsafe.Add(ref sourceBase, i); + Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + } + } + + /// + /// Converts vectors to premultiplied ARGB pixels. + /// + /// The source vectors. + /// The destination pixels. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void FromVector4( + Span source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + + // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ARGB pixels. + /// + /// The source vectors. + /// 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 premultiplied ABGR pixels to vectors. + /// + /// The source pixels. + /// The destination vectors. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToVector4( + ReadOnlySpan source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed ABGR bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span 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(); + ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + } + + /// + /// 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)); + + ref Abgr32P sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Abgr32P pixel = Unsafe.Add(ref sourceBase, i); + Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + } + } + + /// + /// Converts vectors to premultiplied ABGR pixels. + /// + /// The source vectors. + /// The destination pixels. + /// The conversion modifier flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void FromVector4( + Span source, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + + // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ABGR pixels. + /// + /// The source vectors. + /// 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.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 9e649f3c0..33b91d151 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 { @@ -60,14 +57,14 @@ internal static partial class Vector4Converters return; } - // Using the last quarter of 'destination' as a temporary buffer to avoid allocation: + // ToVector4 expands each pixel into a 16-byte vector. Reuse the unwritten destination tail as RGBA staging + // so pixelOperations can reorder the source without allocating a temporary row. int countWithoutLastItem = count - 1; ReadOnlySpan 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])); diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs index 80b096344..13ab9527f 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs @@ -14,7 +14,6 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Bulk; -[Config(typeof(Config.Short))] public abstract class FromVector4 where TPixel : unmanaged, IPixel { @@ -62,14 +61,21 @@ public abstract class FromVector4 => 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 class FromVector4Rgba32 : FromVector4 } /* - 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 | RatioSD | Code Size | Allocated | + |---------------------------- |------ |------------:|-----------:|----------:|------:|--------:|----------:|----------:| + | PixelOperations_Base | 64 | 59.73 ns | 19.644 ns | 1.077 ns | 1.00 | 0.02 | 1,152 B | - | + | PixelOperations_Specialized | 64 | 50.86 ns | 7.372 ns | 0.404 ns | 0.85 | 0.01 | 2,975 B | - | + | PixelOperations_Base | 256 | 214.03 ns | 70.798 ns | 3.881 ns | 1.00 | 0.02 | 1,152 B | - | + | PixelOperations_Specialized | 256 | 70.21 ns | 17.210 ns | 0.943 ns | 0.33 | 0.01 | 2,992 B | - | + | PixelOperations_Base | 2048 | 1,855.02 ns | 443.677 ns | 24.319 ns | 1.00 | 0.02 | 1,152 B | - | + | PixelOperations_Specialized | 2048 | 272.82 ns | 39.302 ns | 2.154 ns | 0.15 | 0.00 | 2,992 B | - | */ } diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs index 6499632b6..9777a2f3f 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs @@ -20,24 +20,48 @@ public class ToVector4_Bgra32 : ToVector4 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 AssociatedAlphaPixelOperations().ToVector4( + this.Configuration, + this.Source.GetSpan(), + this.Destination.GetSpan()); + } + + // BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2) + // AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores + // .NET 8.0.28, X64 RyuJIT x86-64-v4 + // + // | Method | Count | Mean | Error | StdDev | Ratio | Code Size | Allocated | + // |---------------------------- |------ |------------:|-----------:|---------:|------:|----------:|----------:| + // | PixelOperations_Base | 64 | 58.08 ns | 10.122 ns | 0.555 ns | 1.00 | 932 B | - | + // | PixelOperations_Specialized | 64 | 79.63 ns | 9.080 ns | 0.498 ns | 1.37 | 3,688 B | - | + // | PixelOperations_Base | 256 | 224.99 ns | 46.742 ns | 2.562 ns | 1.00 | 958 B | - | + // | PixelOperations_Specialized | 256 | 99.41 ns | 9.728 ns | 0.533 ns | 0.44 | 3,688 B | - | + // | PixelOperations_Base | 2048 | 1,745.77 ns | 143.244 ns | 7.852 ns | 1.00 | 958 B | - | + // | PixelOperations_Specialized | 2048 | 293.00 ns | 44.137 ns | 2.419 ns | 0.17 | 3,704 B | - | } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 9231fc8b4..0c46de470 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Diagnostics.Windows; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Exporters.Json; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Toolchains.InProcess.Emit; @@ -46,6 +47,34 @@ public partial class Config : ManualConfig .WithArguments([new MsBuildArgument("/p:DebugType=portable")])); } + /// + /// 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 000000000..02f25f2ea --- /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.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 2eb821b61..889746453 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -35,6 +35,20 @@ public partial class ColorTests Assert.Equal(source, data); } + [Fact] + public void Rgba32P() + { + Rgba32P source = new(64, 32, 16, 128); + + // Act: + Color color = Color.FromPixel(source); + + // Assert: + Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation); + Assert.Equal(source.ToScaledVector4(), color.ToScaledVector4()); + Assert.Equal(source, color.ToPixel()); + } + [Fact] public void Argb32() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 2e2f0d07d..66537007f 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -114,6 +114,65 @@ public partial class ColorTests 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 c482fc998..2594caeb5 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -18,6 +18,28 @@ public partial class ColorTests 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()); + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index dfd0a6b30..aec8b6c43 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -148,11 +148,11 @@ public partial class SimdUtilsTests { byte[] source = new Random(count).GenerateRandomByteArray(count); float[] result = new float[count]; - float[] expected = source.Select(b => b / 255f).ToArray(); + float[] expected = source.Select(b => b * (1F / byte.MaxValue)).ToArray(); convert(source, result); - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(expected, result); } [Theory] @@ -193,6 +193,45 @@ public partial class SimdUtilsTests } } + [Fact] + public void BulkConvertNormalizedFloatToByteRoundsMidpointsAwayFromZero() + { + float[] midpointValues = Enumerable.Range(0, byte.MaxValue).Select(x => (x + 0.5F) / byte.MaxValue).ToArray(); + float[] source = new float[1024]; + byte[] expected = new byte[source.Length]; + byte[] actual = new byte[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + int value = i % midpointValues.Length; + source[i] = midpointValues[value]; + expected[i] = (byte)(value + 1); + } + + SimdUtils.NormalizedFloatToByteSaturate(source, actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void BulkConvertFloatToByteRoundsMidpointsAwayFromZeroAndClampsOverflows() + { + float[] source = new float[1027]; + byte[] expected = new byte[source.Length]; + byte[] actual = new byte[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + float value = (i % 258) - .5F; + source[i] = value; + expected[i] = (byte)Math.Min(byte.MaxValue, Math.Max(0, value + .5F)); + } + + SimdUtils.FloatToByteSaturate(source, actual); + + Assert.Equal(expected, actual); + } + [Theory] [MemberData(nameof(ArbitraryArraySizes))] public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( diff --git a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs new file mode 100644 index 000000000..dc4c108fc --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -0,0 +1,667 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// 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 +{ + private static readonly ApproximateFloatComparer VectorComparer = new(.005F); + + /// + /// 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)); + } + } + + [Fact] + public void ScaledVectorConversionsUseAssociatedComponents() + { + Vector4 associated = new(.25F, .125F, .0625F, .5F); + + TPixel pixel = TPixel.FromScaledVector4(associated); + + Assert.Equal(associated, pixel.ToScaledVector4(), VectorComparer); + } + + [Fact] + public void FromRgba32AssociatesColorComponents() + { + Rgba32 source = new(192, 128, 64, 128); + Vector4 expected = source.ToScaledVector4(); + Numerics.Premultiply(ref expected); + + TPixel pixel = TPixel.FromRgba32(source); + + Assert.Equal(expected, pixel.ToScaledVector4(), VectorComparer); + } + + [Fact] + public void ToRgba32ReturnsUnassociatedColorComponents() + { + Rgba32 expected = new(192, 128, 64, 128); + TPixel pixel = TPixel.FromRgba32(expected); + + Rgba32 actual = pixel.ToRgba32(); + + AssertRgba32Equal(expected, actual, 3); + } + + [Fact] + public void ColorConversionsPreserveUnassociatedColor() + { + Rgba32 expected = new(192, 128, 64, 128); + TPixel source = TPixel.FromRgba32(expected); + + Color color = Color.FromPixel(source); + Rgba32 actual = color.ToPixel(); + TPixel roundTrip = color.ToPixel(); + + AssertRgba32Equal(expected, actual, 3); + Assert.Equal(source.ToScaledVector4(), roundTrip.ToScaledVector4(), VectorComparer); + } + + [Fact] + public void ScalarBlendingUsesUnassociatedColorValues() + { + TPixel background = TPixel.FromRgba32(new Rgba32(200, 40, 80, 160)); + TPixel source = TPixel.FromRgba32(new Rgba32(20, 180, 100, 96)); + PixelBlender associatedBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + PixelBlender unassociatedBlender = new DefaultPixelBlenders.NormalSrcOver(); + + Rgba32 expected = unassociatedBlender.Blend(background.ToRgba32(), source.ToRgba32(), .75F); + Rgba32 actual = associatedBlender.Blend(background, source, .75F).ToRgba32(); + + AssertRgba32Equal(expected, actual, 4); + } + + private static void AssertRgba32Equal(Rgba32 expected, Rgba32 actual, int tolerance) + { + Assert.InRange(Math.Abs(expected.R - actual.R), 0, tolerance); + Assert.InRange(Math.Abs(expected.G - actual.G), 0, tolerance); + Assert.InRange(Math.Abs(expected.B - actual.B), 0, tolerance); + Assert.InRange(Math.Abs(expected.A - actual.A), 0, tolerance); + } +} + +/// +/// 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); + } +} + +/// +/// 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); + } +} + +/// +/// 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(); + + private static void AssertLosslessRoundTrip() + where TIntermediate : unmanaged, IPixel + { + Rgba32P[] expected = + [ + new(0, 0, 0, 0), + new(1, 2, 3, 4), + new(31, 63, 95, 127), + new(64, 128, 192, 255), + new(255, 255, 255, 255), + ]; + TIntermediate[] intermediate = new TIntermediate[expected.Length]; + Rgba32P[] actual = new Rgba32P[expected.Length]; + + PixelOperations.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 + { + TPixel[] pixels = new TPixel[64]; + Vector4[] actual = new Vector4[pixels.Length]; + + for (int i = 0; i < pixels.Length; i++) + { + int component = i * 4; + pixels[i] = createPixel((byte)component, (byte)(component + 1), (byte)(component + 2), (byte)(component + 3)); + } + + PixelOperations.Instance.ToAssociatedScaledVector4(Configuration.Default, pixels, actual); + + for (int i = 0; i < pixels.Length; i++) + { + Assert.Equal(pixels[i].ToScaledVector4(), actual[i]); + } + } + + private static void AssertScalarAndBulkFromAssociatedVectorsAreEqual() + where TPixel : unmanaged, IPixel + { + const int count = 259; + Vector4[] vectors = new Vector4[count]; + TPixel[] expected = new TPixel[count]; + TPixel[] actual = new TPixel[count]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + for (int i = 0; i < vectors.Length; i++) + { + // Alternating exact byte alpha values with fractional values covers both reassociation branches. The odd length also exercises the SIMD remainder. + float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F; + vectors[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; + vectors[i].W = alpha; + expected[i] = operations.FromAssociatedScaledVector4(vectors[i]); + } + + operations.FromAssociatedScaledVector4(Configuration.Default, vectors, actual); + + Assert.Equal(expected, actual); + } +} + +/// +/// Tests conversion between associated and unassociated packed byte formats. +/// +public class AssociatedToUnassociatedPackedPixelConversionTests +{ + [Fact] + public void Rgba32PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() + { + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared. + for (int associated = 0; associated <= alpha; associated++) + { + // This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha. + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); + Rgba32 expectedUnassociated = new(unassociated, 0, 0, (byte)alpha); + + Rgba32 actualUnassociated = source.ToRgba32(); + Rgba32P actualRoundTrip = Rgba32P.FromRgba32(actualUnassociated); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + } + } + + [Fact] + public void ColorFromRgba32PPreservesEveryValidAssociatedComponent() + { + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); + Color color = Color.FromPixel(source); + + Assert.Equal(new Rgba32(unassociated, 0, 0, (byte)alpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + } + + [Fact] + public void Rgba32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); + + [Fact] + public void Bgra32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); + + [Fact] + public void Argb32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); + + [Fact] + public void Abgr32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); + + [Fact] + public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() + { + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); + + Rgba32 actualUnassociated = source.ToRgba32(); + NormalizedByte4P actualRoundTrip = NormalizedByte4P.FromRgba32(actualUnassociated); + + Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + } + } + + [Fact] + public void ColorFromNormalizedByte4PPreservesEveryValidAssociatedComponent() + { + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); + Color color = Color.FromPixel(source); + + Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + } + + [Fact] + public void NormalizedByte4PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + { + const int pairCount = 32640; + const int channelCount = 3; + NormalizedByte4P[] source = new NormalizedByte4P[pairCount * channelCount]; + Bgra32[] expectedUnassociated = new Bgra32[source.Length]; + Bgra32[] actualUnassociated = new Bgra32[source.Length]; + NormalizedByte4P[] actualRoundTrip = new NormalizedByte4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + + source[index] = CreateNormalizedByte4P(associated, 0, 0, alpha); + expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, unassociatedAlpha); + source[index] = CreateNormalizedByte4P(0, associated, 0, alpha); + expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, unassociatedAlpha); + source[index] = CreateNormalizedByte4P(0, 0, associated, alpha); + expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, unassociatedAlpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + + 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++) + { + // Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared. + for (int associated = 0; associated <= alpha; associated++) + { + // This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha. + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + + source[index] = createPixel((byte)associated, 0, 0, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, (byte)alpha); + source[index] = createPixel(0, (byte)associated, 0, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, (byte)alpha); + source[index] = createPixel(0, 0, (byte)associated, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + + private static NormalizedByte4P CreateNormalizedByte4P(int red, int green, int blue, int alpha) + { + uint packed = (byte)(red - 127) + | ((uint)(byte)(green - 127) << 8) + | ((uint)(byte)(blue - 127) << 16) + | ((uint)(byte)(alpha - 127) << 24); + + return new NormalizedByte4P { PackedValue = packed }; + } +} + +/// +/// 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() + { + 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 nativeAlpha = ((alpha / (float)ushort.MaxValue) * 2F) - 1F; + ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)nativeAlpha); + float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) + 1F) / 2F; + + foreach (byte component in components) + { + float associatedRed = ((component / (float)byte.MaxValue) * storedAlpha * 2F) - 1F; + ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)associatedRed); + HalfVector4P actualScalar = HalfVector4P.FromRgba64(source[index]); + + Assert.Equal(expectedRed, (ushort)actualScalar.PackedValue); + Assert.Equal(expectedAlpha, (ushort)(actualScalar.PackedValue >> 48)); + Assert.Equal(expectedRed, (ushort)actualBulk[index].PackedValue); + Assert.Equal(expectedAlpha, (ushort)(actualBulk[index].PackedValue >> 48)); + index++; + } + } + } + + [Fact] + public void ColorToRgba32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [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(); + + /// + /// 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) * (1F / 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/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 36ddad6c6..d61ab88a6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -222,6 +222,22 @@ public class PixelBlenderTests ExerciseAllBlenderModeCombinations, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void AssociatedAlphaBlendFunctionsAreCalledForAllModeCombinations() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + ExerciseAllAssociatedAlphaBlenderModeCombinations, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void AssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() + { + Rgba32P background = Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)); + Rgba32P source = Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)); + PixelBlender 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 +640,167 @@ public class PixelBlenderTests } } + private static void ExerciseAllAssociatedAlphaBlenderModeCombinations() + { + Rgba32P[] background = + [ + Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)), + Rgba32P.FromRgba32(new Rgba32(40, 200, 100, 192)), + Rgba32P.FromRgba32(new Rgba32(120, 60, 230, 224)), + Rgba32P.FromRgba32(new Rgba32(180, 160, 20, 128)), + Rgba32P.FromRgba32(new Rgba32(30, 140, 210, 96)), + ]; + + Rgba32P[] source = + [ + Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)), + Rgba32P.FromRgba32(new Rgba32(210, 30, 150, 144)), + Rgba32P.FromRgba32(new Rgba32(80, 220, 40, 176)), + Rgba32P.FromRgba32(new Rgba32(240, 110, 60, 208)), + Rgba32P.FromRgba32(new Rgba32(100, 50, 200, 112)), + ]; + + foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues()) + { + 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 +812,7 @@ public class PixelBlenderTests 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 2a5c5765a..935b4acc2 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 abstract partial class PixelConverterTests } 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 @@ public abstract partial class PixelConverterTests return; } - // Normal conversion + // Scalar source and destination boundaries are the reference for the bulk operation: each format owns any representation conversion and destination quantization it requires. ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - dp = TDestinationPixel.FromScaledVector4(sp.ToScaledVector4()); + Vector4 vector = PixelOperations.Instance.ToUnassociatedScaledVector4(sp); + dp = PixelOperations.Instance.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 b37282927..f287a6995 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 SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; - namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; public partial class PixelOperationsTests @@ -43,6 +42,21 @@ public partial class PixelOperationsTests } } + public partial class Argb32P_OperationsTests : PixelOperationsTests + { + 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 partial class PixelOperationsTests } } + 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 partial class PixelOperationsTests } } + 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 partial class PixelOperationsTests } } + 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 partial class PixelOperationsTests } } + 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 partial class PixelOperationsTests } } + 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) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index ccf90fe40..43dec7937 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -33,28 +33,41 @@ using Xunit.Abstractions; "Short4" ]; - private static readonly string[] AssociatedAlphaPixelTypes = []; + private static readonly string[] AssociatedAlphaPixelTypes = + [ + "Argb32P", + "Abgr32P", + "Bgra32P", + "HalfVector4P", + "NormalizedByte4P", + "Rgba32P" + ]; private static readonly string[] CommonPixelTypes = [ "A8", "Argb32", + "Argb32P", "Abgr32", + "Abgr32P", "Bgr24", "Bgr565", "Bgra32", + "Bgra32P", "Bgra4444", "Bgra5551", "Byte4", "HalfSingle", "HalfVector2", "HalfVector4", + "HalfVector4P", "L16", "L8", "La16", "La32", "NormalizedByte2", "NormalizedByte4", + "NormalizedByte4P", "NormalizedShort2", "NormalizedShort4", "Rg32", @@ -62,6 +75,7 @@ using Xunit.Abstractions; "Rgb48", "Rgba1010102", "Rgba32", + "Rgba32P", "Rgba64", "RgbaVector", "Short2", diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 32b62fc03..196c98449 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -70,7 +70,7 @@ public abstract class PixelOperationsTests : MeasureFixture 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) { @@ -185,7 +185,7 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.Premultiply(ref v); } @@ -193,7 +193,7 @@ public abstract class PixelOperationsTests : MeasureFixture void ExpectedAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.UnPremultiply(ref v); } @@ -207,11 +207,11 @@ public abstract class PixelOperationsTests : MeasureFixture expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); + this.Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply); }); } @@ -221,7 +221,7 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.Premultiply(ref v); } @@ -229,7 +229,7 @@ public abstract class PixelOperationsTests : MeasureFixture void ExpectedAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.UnPremultiply(ref v); } @@ -243,15 +243,11 @@ public abstract class PixelOperationsTests : MeasureFixture expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.Scale); + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); }); } @@ -263,7 +259,7 @@ public abstract class PixelOperationsTests : MeasureFixture { v = SRgbCompanding.Expand(v); - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.Premultiply(ref v); } @@ -271,7 +267,7 @@ public abstract class PixelOperationsTests : MeasureFixture void ExpectedAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.UnPremultiply(ref v); } @@ -287,15 +283,11 @@ public abstract class PixelOperationsTests : MeasureFixture expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); }, false); } @@ -317,22 +309,27 @@ public abstract class PixelOperationsTests : MeasureFixture { 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,6 +337,7 @@ public abstract class PixelOperationsTests : MeasureFixture new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), @@ -355,7 +353,7 @@ public abstract class PixelOperationsTests : MeasureFixture 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); } @@ -408,7 +406,13 @@ public abstract class PixelOperationsTests : MeasureFixture { } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); + void ExpectedAction(ref Vector4 v) + { + if (!this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } TPixel[] source = CreatePixelTestData(count, SourceAction); Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction); @@ -427,7 +431,13 @@ public abstract class PixelOperationsTests : MeasureFixture { } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); + void ExpectedAction(ref Vector4 v) + { + if (!this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -453,7 +463,11 @@ public abstract class PixelOperationsTests : MeasureFixture void ExpectedAction(ref Vector4 v) { v = SRgbCompanding.Expand(v); - Numerics.Premultiply(ref v); + + if (!this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); @@ -499,7 +513,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Argb32 argb = Argb32.FromScaledVector4(source[i].ToScaledVector4()); + Argb32 argb = Argb32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = argb.A; expected[i4 + 1] = argb.R; @@ -543,7 +557,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i3 = i * 3; - Bgr24 bgr = Bgr24.FromScaledVector4(source[i].ToScaledVector4()); + Bgr24 bgr = Bgr24.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i3] = bgr.B; expected[i3 + 1] = bgr.G; expected[i3 + 2] = bgr.R; @@ -585,7 +599,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Bgra32 bgra = Bgra32.FromScaledVector4(source[i].ToScaledVector4()); + Bgra32 bgra = Bgra32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = bgra.B; expected[i4 + 1] = bgra.G; expected[i4 + 2] = bgra.R; @@ -628,7 +642,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Abgr32 abgr = Abgr32.FromScaledVector4(source[i].ToScaledVector4()); + Abgr32 abgr = Abgr32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = abgr.A; expected[i4 + 1] = abgr.B; expected[i4 + 2] = abgr.G; @@ -674,7 +688,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - Bgra5551 bgra = Bgra5551.FromScaledVector4(source[i].ToScaledVector4()); + Bgra5551 bgra = Bgra5551.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref bgra); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -714,7 +728,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { - expected[i] = L8.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L8.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -751,7 +765,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { - expected[i] = L16.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L16.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -793,7 +807,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - La16 la = La16.FromScaledVector4(source[i].ToScaledVector4()); + La16 la = La16.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -838,7 +852,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - La32 la = La32.FromScaledVector4(source[i].ToScaledVector4()); + La32 la = La32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -882,7 +896,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i3 = i * 3; - Rgb24 rgb = Rgb24.FromScaledVector4(source[i].ToScaledVector4()); + Rgb24 rgb = Rgb24.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i3] = rgb.R; expected[i3 + 1] = rgb.G; expected[i3 + 2] = rgb.B; @@ -924,7 +938,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Rgba32 rgba = Rgba32.FromScaledVector4(source[i].ToScaledVector4()); + Rgba32 rgba = Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = rgba.R; expected[i4 + 1] = rgba.G; expected[i4 + 2] = rgba.B; @@ -967,7 +981,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i6 = i * 6; - Rgb48 rgb = Rgb48.FromScaledVector4(source[i].ToScaledVector4()); + Rgb48 rgb = Rgb48.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes rgb48Bytes = Unsafe.As(ref rgb); expected[i6] = rgb48Bytes[0]; expected[i6 + 1] = rgb48Bytes[1]; @@ -1013,7 +1027,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i8 = i * 8; - Rgba64 rgba = Rgba64.FromScaledVector4(source[i].ToScaledVector4()); + Rgba64 rgba = Rgba64.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes rgba64Bytes = Unsafe.As(ref rgba); expected[i8] = rgba64Bytes[0]; expected[i8 + 1] = rgba64Bytes[1]; @@ -1072,6 +1086,10 @@ public abstract class PixelOperationsTests : MeasureFixture return expected; } + private static Vector4 ToUnassociatedScaledVector4(TPixel source) + // The scalar conversion boundary owns representation-specific rounding. Byte-export tests compare each bulk operation with that scalar result. + => PixelOperations.Instance.ToUnassociatedScaledVector4(source); + internal static void TestOperation( TSource[] source, TDest[] expected, @@ -1208,15 +1226,34 @@ public abstract class PixelOperationsTests : MeasureFixture Assert.Equal(expected[i], actual[i], comparer); } } - else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) + else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest))) { Span 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/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 87c1fb728..2449c3efd 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 c697cd4c2..37323155f 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 f3b7b0e80..8f63e3277 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_JpegCompressedWithIssue2679_Issue2679.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png index d2f11dc17..aded240a1 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 412af0ac7..2f546df5b 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 35b99df98..4592d77da 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 From 4b07920c7c1ea48aa82ad31815954d9289698370 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Jul 2026 11:59:05 +1000 Subject: [PATCH 205/240] Fix alpha association and SIMD rounding parity Refactors `Color` to track both exposed and stored alpha representations, adds `ToScaledVector4(PixelAlphaRepresentation)`, and adjusts pixel conversion paths so associated formats preserve canonical values without unnecessary unpremultiply/reassociate loss. This also updates equality/hash behavior to compare canonical scaled values. SIMD helpers are split into `MultiplyAddEstimate` vs `FusedMultiplyAdd`, with byte-to-float normalization updated to match scalar rounding exactly across vector widths. JPEG converters, resize kernels, and Porter-Duff/associated-alpha blending paths were updated to use the appropriate helper for either fast estimate or strict fused rounding semantics. Tests were expanded substantially (including exhaustive component/alpha cases and fused-order checks), incidental test cleanup was applied, benchmark comment snapshots were refreshed, and one black/white reference output image was updated. --- src/ImageSharp/Color/Color.cs | 190 ++++++----- .../Common/Helpers/SimdUtils.Convert.cs | 3 +- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 53 ++- .../Common/Helpers/Vector128Utilities.cs | 54 ++- .../Common/Helpers/Vector256Utilities.cs | 45 ++- .../Common/Helpers/Vector512Utilities.cs | 50 ++- .../JpegColorConverter.GrayScaleVector128.cs | 2 +- .../JpegColorConverter.GrayScaleVector256.cs | 2 +- .../JpegColorConverter.GrayScaleVector512.cs | 2 +- .../JpegColorConverter.TiffYccKVector128.cs | 12 +- .../JpegColorConverter.TiffYccKVector256.cs | 12 +- .../JpegColorConverter.TiffYccKVector512.cs | 12 +- .../JpegColorConverter.YCbCrVector128.cs | 12 +- .../JpegColorConverter.YCbCrVector256.cs | 12 +- .../JpegColorConverter.YCbCrVector512.cs | 12 +- .../JpegColorConverter.YccKVector128.cs | 12 +- .../JpegColorConverter.YccKVector256.cs | 12 +- .../JpegColorConverter.YccKVector512.cs | 12 +- .../Components/FloatingPointDCT.Vector256.cs | 8 +- .../AssociatedAlphaPorterDuffFunctions.cs | 16 +- .../PixelBlenders/PorterDuffFunctions.cs | 26 +- .../PixelImplementations/Abgr32.cs | 2 +- .../PixelImplementations/Abgr32P.cs | 4 +- .../PixelImplementations/Argb32.cs | 2 +- .../PixelImplementations/Argb32P.cs | 4 +- .../PixelImplementations/Bgra32.cs | 2 +- .../PixelImplementations/Bgra32P.cs | 4 +- .../PixelImplementations/NormalizedByte4P.cs | 5 +- .../PixelImplementations/Rgba32.cs | 2 +- .../PixelImplementations/Rgba32P.cs | 4 +- .../Transforms/Resize/ResizeKernel.cs | 8 +- .../ImageSharp.Benchmarks/Bulk/FromVector4.cs | 16 +- .../Bulk/ToVector4_Bgra32.cs | 16 +- tests/ImageSharp.Tests/Color/ColorTests.cs | 31 ++ .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 32 +- .../Formats/Icon/Cur/CurEncoderTests.cs | 7 - .../Formats/Qoi/ImageExtensionsTest.cs | 2 +- .../PixelFormats/AssociatedAlphaPixelTests.cs | 312 +++++++++++++----- .../PixelFormats/PixelBlenderTests.cs | 7 +- ...ackWhiteFilter_Rgba32_TestPattern48x48.png | 4 +- 40 files changed, 673 insertions(+), 350 deletions(-) diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 7969f4728..9ce0ecfc3 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -20,45 +20,23 @@ namespace SixLabors.ImageSharp; public readonly partial struct Color : IEquatable { private readonly Vector4 data; - private readonly Vector4 unassociatedData; 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 of . + /// The alpha representation exposed by the color. + /// The alpha representation of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(Vector4 vector, PixelAlphaRepresentation alphaRepresentation) - { - Vector4 clamped = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - Vector4 unassociated = clamped; - - if (alphaRepresentation == PixelAlphaRepresentation.Associated) - { - Numerics.UnPremultiply(ref unassociated); - } - - this.data = clamped; - this.unassociatedData = unassociated; - this.boxedHighPrecisionPixel = null; - this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - /// The unassociated representation of . - /// The alpha representation of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Color(Vector4 vector, Vector4 unassociatedVector, PixelAlphaRepresentation alphaRepresentation) + private Color(Vector4 vector, PixelAlphaRepresentation alphaRepresentation, PixelAlphaRepresentation dataAlphaRepresentation) { this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.unassociatedData = Numerics.Clamp(unassociatedVector, Vector4.Zero, Vector4.One); this.boxedHighPrecisionPixel = null; this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + this.dataIsAssociated = dataAlphaRepresentation == PixelAlphaRepresentation.Associated; } /// @@ -71,8 +49,8 @@ public readonly partial struct Color : IEquatable { this.boxedHighPrecisionPixel = pixel; this.data = default; - this.unassociatedData = default; this.isAssociated = alphaRepresentation == PixelAlphaRepresentation.Associated; + this.dataIsAssociated = this.isAssociated; } /// @@ -118,14 +96,22 @@ public readonly partial struct Color : IEquatable // 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) { - Vector4 vector = source.ToScaledVector4(); - Vector4 unassociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated - ? PixelOperations.Instance.ToUnassociatedScaledVector4(source) - : vector; + int maximumComponentPrecision = info.ComponentInfo.Value.GetMaximumComponentPrecision(); - return new Color(vector, unassociated, info.AlphaRepresentation); + 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 = PixelOperations.Instance.ToUnassociatedScaledVector4(source); + return new Color(vector, info.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); + } + + return new Color(source.ToScaledVector4(), info.AlphaRepresentation, info.AlphaRepresentation); + } } return new Color(source, info.AlphaRepresentation); @@ -137,7 +123,8 @@ public readonly partial struct Color : IEquatable /// The unassociated vector to load the color from. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromScaledVector(Vector4 source) => new(source, PixelAlphaRepresentation.Unassociated); + public static Color FromScaledVector(Vector4 source) + => new(source, PixelAlphaRepresentation.Unassociated, PixelAlphaRepresentation.Unassociated); /// /// Creates a from a generic scaled with the specified alpha representation. @@ -146,7 +133,8 @@ public readonly partial struct Color : IEquatable /// The alpha representation of . /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromScaledVector(Vector4 source, PixelAlphaRepresentation alphaRepresentation) => new(source, alphaRepresentation); + public static Color FromScaledVector(Vector4 source, PixelAlphaRepresentation alphaRepresentation) + => new(source, alphaRepresentation, alphaRepresentation); /// /// Bulk converts a span of generic scaled to a span of . @@ -190,23 +178,38 @@ public readonly partial struct Color : IEquatable // 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) { - bool isAssociated = info.AlphaRepresentation == PixelAlphaRepresentation.Associated; + int maximumComponentPrecision = info.ComponentInfo.Value.GetMaximumComponentPrecision(); - for (int i = 0; i < source.Length; i++) + if (maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit32) { - Vector4 vector = source[i].ToScaledVector4(); - Vector4 unassociated = isAssociated ? PixelOperations.Instance.ToUnassociatedScaledVector4(source[i]) : vector; - destination[i] = new Color(vector, unassociated, info.AlphaRepresentation); + if (info.AlphaRepresentation == PixelAlphaRepresentation.Associated && maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit8) + { + // Match the scalar conversion by retaining exact unassociated values from the format-specific operation. + PixelOperations operations = PixelOperations.Instance; + + for (int i = 0; i < source.Length; i++) + { + Vector4 vector = operations.ToUnassociatedScaledVector4(source[i]); + 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], info.AlphaRepresentation); - } + destination[i] = new Color(source[i], info.AlphaRepresentation); } } @@ -349,26 +352,9 @@ public readonly partial struct Color : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public Color WithAlpha(float alpha) { - Vector4 v = this.ToScaledVector4(); - float clampedAlpha = Numerics.Clamp(alpha, 0, 1); - - if (this.isAssociated) - { - Vector4 unassociated = this.boxedHighPrecisionPixel is null ? this.unassociatedData : v; - - if (this.boxedHighPrecisionPixel is not null) - { - Numerics.UnPremultiply(ref unassociated); - } - - unassociated.W = clampedAlpha; - Vector4 associated = unassociated; - Numerics.Premultiply(ref associated); - return new Color(associated, unassociated, PixelAlphaRepresentation.Associated); - } - - v.W = clampedAlpha; - return FromScaledVector(v, PixelAlphaRepresentation.Unassociated); + Vector4 vector = this.ToScaledVector4(PixelAlphaRepresentation.Unassociated); + vector.W = Numerics.Clamp(alpha, 0, 1); + return new Color(vector, this.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); } /// @@ -411,17 +397,22 @@ public readonly partial struct Color : IEquatable return pixel; } - Vector4 vector = this.boxedHighPrecisionPixel is null - ? this.isAssociated ? this.unassociatedData : this.data - : this.boxedHighPrecisionPixel.ToScaledVector4(); + Vector4 vector = this.boxedHighPrecisionPixel?.ToScaledVector4() ?? this.data; + PixelOperations operations = PixelOperations.Instance; - if (this.isAssociated && this.boxedHighPrecisionPixel is not null) + if (this.dataIsAssociated) { + if (operations is AssociatedAlphaPixelOperations associatedOperations) + { + // Preserve associated components directly while allowing the destination to quantize alpha to its own storage grid. + return associatedOperations.FromAssociatedScaledVector4(vector); + } + Numerics.UnPremultiply(ref vector); } - // Destination pixel operations own association because RGB must use the alpha value representable by that destination format. - return PixelOperations.Instance.FromUnassociatedScaledVector4(vector); + // Unassociated input lets an associated destination quantize alpha before it multiplies the color components. + return operations.FromUnassociatedScaledVector4(vector); } /// @@ -434,12 +425,55 @@ public readonly partial struct Color : IEquatable [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) { - return this.data; + Numerics.Premultiply(ref vector); + } + else + { + Numerics.UnPremultiply(ref vector); } - return this.boxedHighPrecisionPixel.ToScaledVector4(); + return vector; } /// @@ -467,7 +501,7 @@ public readonly partial struct Color : IEquatable { if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) { - return this.data == other.data && this.isAssociated == other.isAssociated; + return this.isAssociated == other.isAssociated && this.ToScaledVector4() == other.ToScaledVector4(); } return this.isAssociated == other.isAssociated @@ -483,7 +517,7 @@ public readonly partial struct Color : IEquatable { if (this.boxedHighPrecisionPixel is null) { - return HashCode.Combine(this.data, this.isAssociated); + return HashCode.Combine(this.ToScaledVector4(), this.isAssociated); } return HashCode.Combine(this.boxedHighPrecisionPixel.ToScaledVector4(), this.isAssociated); diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs index 2efa3f545..3a66be520 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs @@ -70,14 +70,13 @@ internal static partial class SimdUtils [MethodImpl(MethodImplOptions.NoInlining)] private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span destination) { - const float scale = 1F / byte.MaxValue; ref byte sBase = ref MemoryMarshal.GetReference(source); ref float dBase = ref MemoryMarshal.GetReference(destination); for (int i = 0; i < source.Length; i++) { // Match the SIMD conversion so one span cannot contain different float representations of the same byte value. - Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) * scale; + Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) / (float)byte.MaxValue; } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index cb172d145..503a64b90 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -711,6 +711,10 @@ internal static partial class SimdUtils 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 partial class SimdUtils 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 partial class SimdUtils 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 partial class SimdUtils 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 partial class SimdUtils 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 partial class SimdUtils 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 partial class SimdUtils (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); diff --git a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs index 7ea77fadf..cba34e116 100644 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs @@ -363,30 +363,58 @@ internal static class Vector128_ } /// - /// 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 caef699dd..e900465b5 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -114,25 +114,46 @@ internal static class Vector256_ } /// - /// 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(left, right, addend); + } + + 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(vm0, vm1, va); + return Fma.MultiplyAdd(left, right, addend); } - return va + (vm0 * vm1); + // 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 17f9e9c51..b1af0b2af 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -86,19 +86,47 @@ internal static class Vector512_ => 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/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs index 633080706..3c4a64f80 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 @@ internal abstract partial class JpegColorConverterBase 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 0b2b8a119..3d98d1eff 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 @@ internal abstract partial class JpegColorConverterBase 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 0a58b0196..96126ac9d 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 @@ internal abstract partial class JpegColorConverterBase 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 b360c373a..ae3391d42 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 f996522d3..ec6b228ca 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 47168a739..6a0fb93e2 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 @@ internal abstract partial class JpegColorConverterBase // 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 abstract partial class JpegColorConverterBase // 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 01c8508ed..37847a6e6 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 8fdf1004d..fbccf88e2 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 33cb2496c..387917175 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 67b7ee0dc..279c100b2 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 3a586d25f..1bebdfcf4 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 b81a833cd..9c0e1ab74 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 @@ internal abstract partial class JpegColorConverterBase // 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 @@ internal abstract partial class JpegColorConverterBase // 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 bcd8c7043..7c342d7a0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs @@ -55,8 +55,8 @@ internal static partial class FloatingPointDCT 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 @@ internal static partial class FloatingPointDCT 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/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs index 4f6af32c8..d7b5260d3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs @@ -398,7 +398,9 @@ internal static partial class AssociatedAlphaPorterDuffFunctions // Atop discards source-only color and retains the destination alpha unchanged. Vector4 sourceAlpha = Numerics.PermuteW(source); Vector4 destinationAlpha = Numerics.PermuteW(destination); - Vector4 result = (destination * (Vector4.One - sourceAlpha)) + overlap; + Vector4 coefficient = Vector4.One - sourceAlpha; + Vector4 result = Vector128_.FusedMultiplyAdd(destination.AsVector128(), coefficient.AsVector128(), overlap.AsVector128()).AsVector4(); + return Numerics.WithW(result, destinationAlpha); } @@ -408,7 +410,8 @@ internal static partial class AssociatedAlphaPorterDuffFunctions { Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); - Vector256 result = (destination * (Vector256.Create(1F) - sourceAlpha)) + overlap; + Vector256 result = Vector256_.FusedMultiplyAdd(destination, Vector256.Create(1F) - sourceAlpha, overlap); + return Avx.Blend(result, destinationAlpha, BlendAlphaControl); } @@ -418,7 +421,8 @@ internal static partial class AssociatedAlphaPorterDuffFunctions { Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); - Vector512 result = (destination * (Vector512.Create(1F) - sourceAlpha)) + overlap; + Vector512 result = Vector512_.FusedMultiplyAdd(destination, Vector512.Create(1F) - sourceAlpha, overlap); + return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result); } @@ -506,18 +510,18 @@ internal static partial class AssociatedAlphaPorterDuffFunctions public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage) { // Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths. - return Vector128_.MultiplyAdd(backdrop.AsVector128(), (source - backdrop).AsVector128(), Vector128.Create(coverage)).AsVector4(); + 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_.MultiplyAdd(backdrop, source - backdrop, coverage); + => Vector256_.FusedMultiplyAdd(source - backdrop, coverage, backdrop); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 BlendWithCoverage(Vector512 backdrop, Vector512 source, Vector512 coverage) - => Vector512_.MultiplyAdd(backdrop, source - backdrop, coverage); + => Vector512_.FusedMultiplyAdd(source - backdrop, coverage, backdrop); /// /// Calculates one associated Overlay overlap component without recovering either straight component. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 1e5fc8df9..46d97ad3d 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -340,7 +340,7 @@ internal static partial class PorterDuffFunctions Vector4 sourcePremultiplied = Numerics.WithW(source * sourceAlpha, sourceAlpha); // Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths. - Vector4 result = Vector128_.MultiplyAdd(backdropPremultiplied.AsVector128(), (sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage)).AsVector4(); + Vector4 result = Vector128_.MultiplyAddEstimate((sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage), backdropPremultiplied.AsVector128()).AsVector4(); Numerics.UnPremultiply(ref result); return result; @@ -360,7 +360,7 @@ internal static partial class PorterDuffFunctions 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)); } @@ -380,7 +380,7 @@ internal static partial class PorterDuffFunctions 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)); } @@ -483,8 +483,8 @@ internal static partial class PorterDuffFunctions // calculate final color 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); @@ -513,8 +513,8 @@ internal static partial class PorterDuffFunctions // calculate final color 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); @@ -567,7 +567,7 @@ internal static partial class PorterDuffFunctions 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); @@ -592,7 +592,7 @@ internal static partial class PorterDuffFunctions 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); @@ -751,8 +751,8 @@ internal static partial class PorterDuffFunctions 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); @@ -776,8 +776,8 @@ internal static partial class PorterDuffFunctions 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/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 1190d307a..ff95e714c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -182,7 +182,7 @@ public partial struct Abgr32 : IPixel, IPackedVector /// [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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs index 780dbd2f8..0d1730c80 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.PixelFormats; [StructLayout(LayoutKind.Sequential)] public partial struct Abgr32P : IPixel, IPackedVector { - private const float ByteScale = 1F / byte.MaxValue; - private static readonly Vector4 Half = new(0.5F); private static readonly Vector4 MaxBytes = new(byte.MaxValue); @@ -147,7 +145,7 @@ public partial struct Abgr32P : IPixel, IPackedVector => 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) * ByteScale; + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public readonly Vector4 ToVector4() => this.ToScaledVector4(); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index b74f5db71..7e40c2753 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -175,7 +175,7 @@ public partial struct Argb32 : IPixel, IPackedVector /// [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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs index fac4f7701..d5069638c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.PixelFormats; [StructLayout(LayoutKind.Sequential)] public partial struct Argb32P : IPixel, IPackedVector { - private const float ByteScale = 1F / byte.MaxValue; - private static readonly Vector4 Half = new(0.5F); private static readonly Vector4 MaxBytes = new(byte.MaxValue); @@ -147,7 +145,7 @@ public partial struct Argb32P : IPixel, IPackedVector => 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) * ByteScale; + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public readonly Vector4 ToVector4() => this.ToScaledVector4(); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index 903d9dc8c..8c47032e7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -124,7 +124,7 @@ public partial struct Bgra32 : IPixel, IPackedVector /// [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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs index a69dfeb46..bc050d148 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.PixelFormats; [StructLayout(LayoutKind.Sequential)] public partial struct Bgra32P : IPixel, IPackedVector { - private const float ByteScale = 1F / byte.MaxValue; - private static readonly Vector4 Half = new(0.5F); private static readonly Vector4 MaxBytes = new(byte.MaxValue); @@ -147,7 +145,7 @@ public partial struct Bgra32P : IPixel, IPackedVector => 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) * ByteScale; + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public readonly Vector4 ToVector4() => this.ToScaledVector4(); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs index 6a3182777..b6549b7dd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -158,10 +158,7 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector /// /// The unassociated scaled vector. /// The associated pixel. - private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) - { - return FromScaledVector4(Associate(source)); - } + private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(Associate(source)); /// /// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination. diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 1eafab854..0cec0b30d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -220,7 +220,7 @@ public partial struct Rgba32 : IPixel, IPackedVector /// [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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs index 46b6a4f65..6bf2e1db0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.PixelFormats; [StructLayout(LayoutKind.Sequential)] public partial struct Rgba32P : IPixel, IPackedVector { - private const float ByteScale = 1F / byte.MaxValue; - private static readonly Vector4 Half = new(0.5F); private static readonly Vector4 MaxBytes = new(byte.MaxValue); @@ -147,7 +145,7 @@ public partial struct Rgba32P : IPixel, IPackedVector => 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) * ByteScale; + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public readonly Vector4 ToVector4() => this.ToScaledVector4(); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index a85487d1c..c0380224c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -104,8 +104,8 @@ internal readonly unsafe struct ResizeKernel 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 @@ internal readonly unsafe struct ResizeKernel 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 @@ internal readonly unsafe struct ResizeKernel 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/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs index 13ab9527f..dcd26c328 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs @@ -159,13 +159,13 @@ public class FromVector4Bgra32P : FromVector4 AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores .NET 8.0.28, X64 RyuJIT x86-64-v4 - | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Allocated | - |---------------------------- |------ |------------:|-----------:|----------:|------:|--------:|----------:|----------:| - | PixelOperations_Base | 64 | 59.73 ns | 19.644 ns | 1.077 ns | 1.00 | 0.02 | 1,152 B | - | - | PixelOperations_Specialized | 64 | 50.86 ns | 7.372 ns | 0.404 ns | 0.85 | 0.01 | 2,975 B | - | - | PixelOperations_Base | 256 | 214.03 ns | 70.798 ns | 3.881 ns | 1.00 | 0.02 | 1,152 B | - | - | PixelOperations_Specialized | 256 | 70.21 ns | 17.210 ns | 0.943 ns | 0.33 | 0.01 | 2,992 B | - | - | PixelOperations_Base | 2048 | 1,855.02 ns | 443.677 ns | 24.319 ns | 1.00 | 0.02 | 1,152 B | - | - | PixelOperations_Specialized | 2048 | 272.82 ns | 39.302 ns | 2.154 ns | 0.15 | 0.00 | 2,992 B | - | + | 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 9777a2f3f..20c38d61f 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs @@ -56,12 +56,12 @@ public class ToVector4_Bgra32P : ToVector4 // AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores // .NET 8.0.28, X64 RyuJIT x86-64-v4 // - // | Method | Count | Mean | Error | StdDev | Ratio | Code Size | Allocated | - // |---------------------------- |------ |------------:|-----------:|---------:|------:|----------:|----------:| - // | PixelOperations_Base | 64 | 58.08 ns | 10.122 ns | 0.555 ns | 1.00 | 932 B | - | - // | PixelOperations_Specialized | 64 | 79.63 ns | 9.080 ns | 0.498 ns | 1.37 | 3,688 B | - | - // | PixelOperations_Base | 256 | 224.99 ns | 46.742 ns | 2.562 ns | 1.00 | 958 B | - | - // | PixelOperations_Specialized | 256 | 99.41 ns | 9.728 ns | 0.533 ns | 0.44 | 3,688 B | - | - // | PixelOperations_Base | 2048 | 1,745.77 ns | 143.244 ns | 7.852 ns | 1.00 | 958 B | - | - // | PixelOperations_Specialized | 2048 | 293.00 ns | 44.137 ns | 2.419 ns | 0.17 | 3,704 B | - | + // | 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.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index 2594caeb5..6c205888a 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; @@ -40,6 +41,36 @@ public partial class ColorTests 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/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index aec8b6c43..71335032c 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 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX); } [Theory] @@ -142,13 +144,37 @@ public partial class SimdUtilsTests 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 * (1F / byte.MaxValue)).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); diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index f895afbd5..c41f455cd 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 class CurEncoderTests 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 31ec27da0..5ec23b146 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/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs index dc4c108fc..f34ca4e05 100644 --- a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -5,7 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -17,8 +16,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats; public abstract class AssociatedAlphaPixelTests where TPixel : unmanaged, IPixel { - private static readonly ApproximateFloatComparer VectorComparer = new(.005F); - /// /// Gets the color channels described by the pixel format. /// @@ -42,75 +39,6 @@ public abstract class AssociatedAlphaPixelTests Assert.Equal(expectedComponentPrecision, componentInfo.GetComponentPrecision(i)); } } - - [Fact] - public void ScaledVectorConversionsUseAssociatedComponents() - { - Vector4 associated = new(.25F, .125F, .0625F, .5F); - - TPixel pixel = TPixel.FromScaledVector4(associated); - - Assert.Equal(associated, pixel.ToScaledVector4(), VectorComparer); - } - - [Fact] - public void FromRgba32AssociatesColorComponents() - { - Rgba32 source = new(192, 128, 64, 128); - Vector4 expected = source.ToScaledVector4(); - Numerics.Premultiply(ref expected); - - TPixel pixel = TPixel.FromRgba32(source); - - Assert.Equal(expected, pixel.ToScaledVector4(), VectorComparer); - } - - [Fact] - public void ToRgba32ReturnsUnassociatedColorComponents() - { - Rgba32 expected = new(192, 128, 64, 128); - TPixel pixel = TPixel.FromRgba32(expected); - - Rgba32 actual = pixel.ToRgba32(); - - AssertRgba32Equal(expected, actual, 3); - } - - [Fact] - public void ColorConversionsPreserveUnassociatedColor() - { - Rgba32 expected = new(192, 128, 64, 128); - TPixel source = TPixel.FromRgba32(expected); - - Color color = Color.FromPixel(source); - Rgba32 actual = color.ToPixel(); - TPixel roundTrip = color.ToPixel(); - - AssertRgba32Equal(expected, actual, 3); - Assert.Equal(source.ToScaledVector4(), roundTrip.ToScaledVector4(), VectorComparer); - } - - [Fact] - public void ScalarBlendingUsesUnassociatedColorValues() - { - TPixel background = TPixel.FromRgba32(new Rgba32(200, 40, 80, 160)); - TPixel source = TPixel.FromRgba32(new Rgba32(20, 180, 100, 96)); - PixelBlender associatedBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - PixelBlender unassociatedBlender = new DefaultPixelBlenders.NormalSrcOver(); - - Rgba32 expected = unassociatedBlender.Blend(background.ToRgba32(), source.ToRgba32(), .75F); - Rgba32 actual = associatedBlender.Blend(background, source, .75F).ToRgba32(); - - AssertRgba32Equal(expected, actual, 4); - } - - private static void AssertRgba32Equal(Rgba32 expected, Rgba32 actual, int tolerance) - { - Assert.InRange(Math.Abs(expected.R - actual.R), 0, tolerance); - Assert.InRange(Math.Abs(expected.G - actual.G), 0, tolerance); - Assert.InRange(Math.Abs(expected.B - actual.B), 0, tolerance); - Assert.InRange(Math.Abs(expected.A - actual.A), 0, tolerance); - } } /// @@ -205,6 +133,29 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue); } + + [Fact] + public void ColorRoundTripDoesNotIntroduceAdditionalAssociationLoss() + { + const ulong zeroComponent = 0xBC00; + + // This is the first valid Half component/alpha pair for which an unassociate/reassociate round trip + // selects the adjacent component value instead of the direct scaled-vector conversion result. + HalfVector4P source = new() + { + PackedValue = 0x8BF5 | (zeroComponent << 16) | (zeroComponent << 32) | (0x05DFUL << 48) + }; + 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()); + } } /// @@ -248,17 +199,19 @@ public class AssociatedAlphaPackedPixelConversionTests private static void AssertLosslessRoundTrip() where TIntermediate : unmanaged, IPixel { - Rgba32P[] expected = - [ - new(0, 0, 0, 0), - new(1, 2, 3, 4), - new(31, 63, 95, 127), - new(64, 128, 192, 255), - new(255, 255, 255, 255), - ]; + Rgba32P[] expected = new Rgba32P[byte.MaxValue * 4 + 4]; TIntermediate[] intermediate = new TIntermediate[expected.Length]; Rgba32P[] actual = new Rgba32P[expected.Length]; + for (int component = 0; component <= byte.MaxValue; component++) + { + int index = component * 4; + expected[index] = new Rgba32P((byte)component, 0, 0, byte.MaxValue); + expected[index + 1] = new Rgba32P(0, (byte)component, 0, byte.MaxValue); + expected[index + 2] = new Rgba32P(0, 0, (byte)component, byte.MaxValue); + expected[index + 3] = new Rgba32P(0, 0, 0, (byte)component); + } + PixelOperations.Instance.From(Configuration.Default, expected, intermediate); PixelOperations.Instance.From(Configuration.Default, intermediate, actual); @@ -273,13 +226,16 @@ public class AssociatedAlphaPackedPixelConversionTests private static void AssertScalarAndBulkAssociatedVectorsAreEqual(Func createPixel) where TPixel : unmanaged, IPixel { - TPixel[] pixels = new TPixel[64]; + TPixel[] pixels = new TPixel[byte.MaxValue * 4 + 4]; Vector4[] actual = new Vector4[pixels.Length]; - for (int i = 0; i < pixels.Length; i++) + for (int component = 0; component <= byte.MaxValue; component++) { - int component = i * 4; - pixels[i] = createPixel((byte)component, (byte)(component + 1), (byte)(component + 2), (byte)(component + 3)); + int index = component * 4; + pixels[index] = createPixel((byte)component, 0, 0, byte.MaxValue); + pixels[index + 1] = createPixel(0, (byte)component, 0, byte.MaxValue); + pixels[index + 2] = createPixel(0, 0, (byte)component, byte.MaxValue); + pixels[index + 3] = createPixel(0, 0, 0, (byte)component); } PixelOperations.Instance.ToAssociatedScaledVector4(Configuration.Default, pixels, actual); @@ -319,6 +275,110 @@ public class AssociatedAlphaPackedPixelConversionTests /// 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; + + // HalfVector4 maps scaled zero to native -1, whose IEEE 754 binary16 representation is 0xBC00. + const ulong zeroComponentBits = 0xBC00; + 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++) + { + // HalfVector4 stores normalized values over -1 through 1. 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); + float nativeAlpha = (normalizedAlpha * 2F) - 1F; + ushort alphaBits = BitConverter.HalfToUInt16Bits((Half)nativeAlpha); + float destinationAlpha = ((float)BitConverter.UInt16BitsToHalf(alphaBits) + 1F) * .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 * 2F) - 1F)); + 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 Rgba32PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() { @@ -344,6 +404,11 @@ public class AssociatedToUnassociatedPackedPixelConversionTests [Fact] public void ColorFromRgba32PPreservesEveryValidAssociatedComponent() { + 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++) @@ -351,11 +416,19 @@ public class AssociatedToUnassociatedPackedPixelConversionTests 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] @@ -397,6 +470,11 @@ public class AssociatedToUnassociatedPackedPixelConversionTests [Fact] public void ColorFromNormalizedByte4PPreservesEveryValidAssociatedComponent() { + 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++) @@ -405,11 +483,19 @@ public class AssociatedToUnassociatedPackedPixelConversionTests 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] @@ -444,6 +530,15 @@ public class AssociatedToUnassociatedPackedPixelConversionTests 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) @@ -479,6 +574,53 @@ public class AssociatedToUnassociatedPackedPixelConversionTests 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) @@ -638,7 +780,7 @@ public class AssociatedDestinationAlphaQuantizationTests foreach (byte component in components) { int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue; - Vector4 expected = new Vector4(expectedRed, 0, 0, expectedAlpha) * (1F / 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()); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index d61ab88a6..a851e2fcf 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -229,7 +229,12 @@ public class PixelBlenderTests HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); [Fact] - public void AssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() + 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)); 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 4f57775dc..aa561ace8 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 From 3d3f49fab88a68735202f6b789d4695cf4f61620 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Jul 2026 15:53:34 +1000 Subject: [PATCH 206/240] Refine alpha conversion and AOT seeding Introduce explicit associated/unassociated pixel conversion APIs across `IPixel` and `PixelOperations`, and rework associated-alpha operations to use representation-aware bulk hooks. Update pixel implementations, converters, blenders, and generated code to consistently apply `Premultiply`/`UnPremultiply` semantics, plus SIMD-backed conversion paths for half and signed formats. Fix codec and processing behavior where alpha handling was incorrect or lossy (notably TIFF/EXR decode/encode, convolution/resize/filter/normalization/dithering paths), and add targeted tests for these cases. Expand AOT compiler roots (including EXR and additional pixel/processor coverage), add a new public API test project for pixel-operations extensibility, and add broad alpha-representation and quantization regression coverage. --- ImageSharp.sln | 7 + src/ImageSharp/Advanced/AotCompilerTools.cs | 12 + src/ImageSharp/Color/Color.cs | 19 +- .../Common/Helpers/ColorNumerics.cs | 2 +- src/ImageSharp/Common/Helpers/Numerics.cs | 210 +++-- .../Common/Helpers/Vector128Utilities.cs | 3 +- .../Common/Helpers/Vector256Utilities.cs | 3 +- .../Common/Helpers/Vector512Utilities.cs | 11 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 10 +- src/ImageSharp/Formats/EncodingUtilities.cs | 4 +- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 9 +- src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | 7 +- src/ImageSharp/Formats/Exr/ExrMetadata.cs | 3 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../BlackIsZero16TiffColor{TPixel}.cs | 2 +- .../BlackIsZero32FloatTiffColor{TPixel}.cs | 4 +- .../BlackIsZeroTiffColor{TPixel}.cs | 2 +- .../CieLab16PlanarTiffColor{TPixel}.cs | 4 +- .../CieLab16TiffColor{TPixel}.cs | 4 +- .../CieLab8PlanarTiffColor{TPixel}.cs | 2 +- .../CieLab8TiffColor{TPixel}.cs | 2 +- .../CmykTiffColor{TPixel}.cs | 4 +- .../PaletteTiffColor{TPixel}.cs | 4 +- .../Rgb444TiffColor{TPixel}.cs | 4 +- .../RgbFloat323232TiffColor{TPixel}.cs | 4 +- .../RgbPlanarTiffColor{TPixel}.cs | 2 +- .../RgbTiffColor{TPixel}.cs | 2 +- .../Rgba16161616TiffColor{TPixel}.cs | 22 +- .../Rgba8888TiffColor{TPixel}.cs | 21 +- .../RgbaFloat32323232TiffColor{TPixel}.cs | 20 +- .../RgbaPlanarTiffColor{TPixel}.cs | 4 +- .../RgbaTiffColor{TPixel}.cs | 6 +- .../TiffColorDecoderFactory{TPixel}.cs | 2 +- .../WhiteIsZero32FloatTiffColor{TPixel}.cs | 4 +- .../WhiteIsZeroTiffColor{TPixel}.cs | 2 +- .../Formats/Tiff/Utils/TiffUtilities.cs | 45 +- .../AssociatedAlphaPixelOperations{TPixel}.cs | 327 +++++--- src/ImageSharp/PixelFormats/HalfTypeHelper.cs | 266 ++++++ src/ImageSharp/PixelFormats/IPixel.cs | 118 ++- .../AssociatedAlphaPixelBlenders.Generated.cs | 216 ++--- .../AssociatedAlphaPixelBlenders.Generated.tt | 2 +- .../AssociatedAlphaPixelBlender{TPixel}.cs | 16 +- .../DefaultPixelBlenders.Generated.cs | 216 ++--- .../DefaultPixelBlenders.Generated.tt | 2 +- .../PorterDuffFunctions.Generated.cs | 216 ++--- .../PorterDuffFunctions.Generated.tt | 2 +- .../PixelFormats/PixelBlender{TPixel}.cs | 6 +- .../PixelFormats/PixelConversionModifiers.cs | 20 +- .../PixelFormats/PixelImplementations/A8.cs | 32 + .../PixelImplementations/Abgr32.cs | 41 + .../PixelImplementations/Abgr32P.cs | 56 +- .../PixelImplementations/Argb32.cs | 41 + .../PixelImplementations/Argb32P.cs | 56 +- .../PixelImplementations/Bgr24.cs | 42 + .../PixelImplementations/Bgr565.cs | 42 + .../PixelImplementations/Bgra32.cs | 41 + .../PixelImplementations/Bgra32P.cs | 56 +- .../PixelImplementations/Bgra4444.cs | 41 + .../PixelImplementations/Bgra5551.cs | 41 + .../PixelImplementations/Byte4.cs | 52 ++ .../PixelImplementations/HalfSingle.cs | 42 + .../PixelImplementations/HalfVector2.cs | 43 + .../PixelImplementations/HalfVector4.cs | 55 ++ .../PixelImplementations/HalfVector4P.cs | 126 +-- .../PixelFormats/PixelImplementations/L16.cs | 42 + .../PixelFormats/PixelImplementations/L8.cs | 42 + .../PixelFormats/PixelImplementations/La16.cs | 41 + .../PixelFormats/PixelImplementations/La32.cs | 41 + .../PixelImplementations/NormalizedByte2.cs | 43 + .../PixelImplementations/NormalizedByte4.cs | 68 +- .../PixelImplementations/NormalizedByte4P.cs | 125 +-- .../PixelImplementations/NormalizedShort2.cs | 43 + .../PixelImplementations/NormalizedShort4.cs | 68 +- .../PixelOperations/A8.PixelOperations.cs | 19 +- .../Abgr32P.PixelOperations.cs | 54 +- .../Argb32P.PixelOperations.cs | 54 +- .../PixelOperations/Bgr565.PixelOperations.cs | 13 +- .../Bgra32P.PixelOperations.cs | 54 +- .../PixelOperations/Byte4.PixelOperations.cs | 94 ++- .../HalfSingle.PixelOperations.cs | 25 +- .../HalfVector2.PixelOperations.cs | 25 +- .../HalfVector4.PixelOperations.cs | 90 ++- .../HalfVector4P.PixelOperations.cs | 745 ++++++++++++++++- .../PixelOperations/L16.PixelOperations.cs | 13 +- .../PixelOperations/L8.PixelOperations.cs | 13 +- .../NormalizedByte2.PixelOperations.cs | 25 +- .../NormalizedByte4.PixelOperations.cs | 90 ++- .../NormalizedByte4P.PixelOperations.cs | 756 +++++++++++++++++- .../NormalizedShort2.PixelOperations.cs | 25 +- .../NormalizedShort4.PixelOperations.cs | 90 ++- .../PixelOperations/Rg32.PixelOperations.cs | 13 +- .../PixelOperations/Rgb48.PixelOperations.cs | 13 +- .../PixelOperations/Rgb96.PixelOperations.cs | 26 + .../Rgba32P.PixelOperations.cs | 54 +- .../RgbaVector.PixelOperations.cs | 13 +- .../PixelOperations/Short2.PixelOperations.cs | 26 +- .../PixelOperations/Short4.PixelOperations.cs | 91 ++- .../PixelFormats/PixelImplementations/Rg32.cs | 42 + .../PixelImplementations/Rgb24.cs | 42 + .../PixelImplementations/Rgb48.cs | 42 + .../PixelImplementations/Rgb96.cs | 44 +- .../PixelImplementations/Rgba1010102.cs | 41 + .../PixelImplementations/Rgba128.cs | 41 + .../PixelImplementations/Rgba32.cs | 41 + .../PixelImplementations/Rgba32P.cs | 56 +- .../PixelImplementations/Rgba64.cs | 41 + .../PixelImplementations/RgbaVector.cs | 41 + .../PixelImplementations/Short2.cs | 43 + .../PixelImplementations/Short4.cs | 55 ++ .../PixelOperations{TPixel}.Generated.cs | 26 +- .../PixelOperations{TPixel}.Generated.tt | 2 +- .../PixelFormats/PixelOperations{TPixel}.cs | 215 +++-- .../Utils/SignedShort4PixelOperations.cs | 400 +++++++++ .../Utils/Vector4Converters.Affine.cs | 134 ++++ ...tor4Converters.AssociatedRgbaCompatible.cs | 562 +++++++++---- .../Utils/Vector4Converters.Default.cs | 215 ++++- .../Utils/Vector4Converters.RgbaCompatible.cs | 1 + .../PixelFormats/Utils/Vector4Converters.cs | 4 +- .../Convolution/BokehBlurProcessor{TPixel}.cs | 21 +- .../Convolution2DRowOperation{TPixel}.cs | 20 +- .../Convolution2PassProcessor{TPixel}.cs | 37 +- .../ConvolutionProcessor{TPixel}.cs | 17 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 6 +- .../Convolution/MedianRowOperation{TPixel}.cs | 4 +- .../Processors/Dithering/ErrorDither.cs | 8 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 8 +- .../Filters/FilterProcessor{TPixel}.cs | 7 +- .../Filters/OpaqueProcessor{TPixel}.cs | 7 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 37 +- ...alizationSlidingWindowProcessor{TPixel}.cs | 18 +- .../AutoLevelProcessor{TPixel}.cs | 22 +- ...lHistogramEqualizationProcessor{TPixel}.cs | 9 +- .../GrayscaleLevelsRowOperation{TPixel}.cs | 6 +- .../HistogramEqualizationProcessor{TPixel}.cs | 5 +- .../HexadecatreeQuantizer{TPixel}.cs | 6 +- .../Quantization/QuantizerUtilities.cs | 26 +- .../Quantization/WuQuantizer{TPixel}.cs | 3 +- .../EntropyCropProcessor{TPixel}.cs | 11 +- .../AffineTransformProcessor{TPixel}.cs | 20 +- .../ProjectiveTransformProcessor{TPixel}.cs | 20 +- .../Resize/ResizeProcessor{TPixel}.cs | 19 +- .../Transforms/Resize/ResizeWorker.cs | 8 +- .../Bulk/ToVector4_Bgra32.cs | 2 +- .../ImageSharp.PublicApi.Tests.csproj | 23 + .../PixelOperationsExtensibilityTests.cs | 163 ++++ tests/ImageSharp.Tests/Color/RgbaDouble.cs | 41 + .../Formats/Exr/ExrMetadataTests.cs | 11 + .../RgbaTiffColorTests.cs | 53 ++ .../Formats/Tiff/TiffDecoderTests.cs | 11 +- .../Formats/Tiff/Utils/TiffUtilitiesTest.cs | 28 +- .../Helpers/ColorNumericsTests.cs | 2 +- .../ImageSharp.Tests/Helpers/NumericsTests.cs | 51 +- .../PixelFormats/AssociatedAlphaPixelTests.cs | 565 ++++++++++++- .../PixelAlphaRepresentationTests.cs | 493 ++++++++++++ ...ConverterTests.ReferenceImplementations.cs | 4 +- .../PixelOperations/PixelOperationsTests.cs | 115 +-- .../AlphaAssociationProcessorTests.cs | 441 ++++++++++ .../HexadecatreeQuantizerTests.cs | 14 + .../Processors/Transforms/ResizeTests.cs | 67 ++ .../AlphaAssociationQuantizerTests.cs | 118 +++ tests/ImageSharp.Tests/TestFormat.cs | 24 + .../BasicTestPatternProvider.cs | 2 +- .../ImageProviders/TestPatternProvider.cs | 14 +- .../TestUtilities/ImagingTestCaseUtility.cs | 2 +- .../TestUtilities/ScaledRgbaVectorP.cs | 271 +++++++ .../TestUtilities/TestImageExtensions.cs | 7 +- .../TestUtilities/TestPixel.cs | 2 +- .../Tests/TestUtilityExtensionsTests.cs | 2 +- ...ype_Uint_Rgba32_rgba_uint_uncompressed.png | 4 +- ...ha_Rgba64_RgbaAssociatedAlpha16bit_msb.png | 4 +- 170 files changed, 9134 insertions(+), 1449 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs create mode 100644 tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj create mode 100644 tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs create mode 100644 tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs create mode 100644 tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs diff --git a/ImageSharp.sln b/ImageSharp.sln index 7a5204037..7e392eaa9 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 ce41e3a0d..bccbcc11a 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,6 +7,7 @@ using System.Numerics; 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; @@ -107,7 +108,9 @@ internal static class AotCompilerTools Seed(); Seed(); Seed(); + Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -190,7 +193,9 @@ internal static class AotCompilerTools 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); @@ -220,6 +225,7 @@ internal static class AotCompilerTools 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); @@ -239,6 +245,7 @@ internal static class AotCompilerTools 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); @@ -259,6 +266,7 @@ internal static class AotCompilerTools { AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); @@ -277,6 +285,7 @@ internal static class AotCompilerTools { AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); @@ -348,6 +357,7 @@ internal static class AotCompilerTools AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); + AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); AotCompileImageProcessor(); @@ -380,11 +390,13 @@ internal static class AotCompilerTools 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 9ce0ecfc3..85e300233 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -106,7 +106,7 @@ public readonly partial struct Color : IEquatable { // 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 = PixelOperations.Instance.ToUnassociatedScaledVector4(source); + Vector4 vector = source.ToUnassociatedScaledVector4(); return new Color(vector, info.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); } @@ -187,11 +187,9 @@ public readonly partial struct Color : IEquatable if (info.AlphaRepresentation == PixelAlphaRepresentation.Associated && maximumComponentPrecision <= (int)PixelComponentBitDepth.Bit8) { // Match the scalar conversion by retaining exact unassociated values from the format-specific operation. - PixelOperations operations = PixelOperations.Instance; - for (int i = 0; i < source.Length; i++) { - Vector4 vector = operations.ToUnassociatedScaledVector4(source[i]); + Vector4 vector = source[i].ToUnassociatedScaledVector4(); destination[i] = new Color(vector, info.AlphaRepresentation, PixelAlphaRepresentation.Unassociated); } @@ -398,21 +396,14 @@ public readonly partial struct Color : IEquatable } Vector4 vector = this.boxedHighPrecisionPixel?.ToScaledVector4() ?? this.data; - PixelOperations operations = PixelOperations.Instance; - if (this.dataIsAssociated) { - if (operations is AssociatedAlphaPixelOperations associatedOperations) - { - // Preserve associated components directly while allowing the destination to quantize alpha to its own storage grid. - return associatedOperations.FromAssociatedScaledVector4(vector); - } - - Numerics.UnPremultiply(ref vector); + // Preserve associated components directly while allowing the destination to quantize alpha to its own storage grid. + return TPixel.FromAssociatedScaledVector4(vector); } // Unassociated input lets an associated destination quantize alpha before it multiplies the color components. - return operations.FromUnassociatedScaledVector4(vector); + return TPixel.FromUnassociatedScaledVector4(vector); } /// diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 066dbec3d..0b88aa5b0 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 04ed48e21..5ffed8eb7 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -6,6 +6,7 @@ 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; @@ -513,7 +514,7 @@ internal static class Numerics /// /// 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 @@ internal static class Numerics } /// - /// 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 @@ internal static class Numerics 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/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs index cba34e116..ce8af4d6f 100644 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs @@ -336,7 +336,8 @@ internal static class Vector128_ /// 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) { diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs index e900465b5..1f3ebb35f 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -97,7 +97,8 @@ internal static class Vector256_ /// 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) { diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs index b1af0b2af..0193a51ba 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -76,14 +76,15 @@ internal static class Vector512_ /// 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); /// /// Computes an estimate of ( * ) + . diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 519b0d536..aba1243d7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -350,10 +350,10 @@ internal sealed class BmpDecoderCore : ImageDecoderCore 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 @@ internal sealed class BmpDecoderCore : ImageDecoderCore 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 @@ internal sealed class BmpDecoderCore : ImageDecoderCore g * invMaxValueGreen, b * invMaxValueBlue, alpha); - pixelRow[x] = TPixel.FromScaledVector4(vector4); + pixelRow[x] = TPixel.FromUnassociatedScaledVector4(vector4); } else { diff --git a/src/ImageSharp/Formats/EncodingUtilities.cs b/src/ImageSharp/Formats/EncodingUtilities.cs index 399474393..64781f4b8 100644 --- a/src/ImageSharp/Formats/EncodingUtilities.cs +++ b/src/ImageSharp/Formats/EncodingUtilities.cs @@ -65,9 +65,9 @@ internal static class EncodingUtilities 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 c24352d57..a0bbdd30e 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -209,7 +209,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore 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()); + + // OpenEXR channel values are associated. The destination conversion retains that representation for associated + // pixels and unassociates only when the destination stores straight alpha. + pixelRow[x] = TPixel.FromAssociatedVector4(pixelValue.ToVector4()); } decodedRows++; @@ -288,7 +291,9 @@ internal sealed class ExrDecoderCore : ImageDecoderCore 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()); + + // Preserve the file's associated-alpha convention while converting from the native unsigned channel domain. + pixelRow[x] = TPixel.FromAssociatedVector4(pixelValue.ToVector4()); } decodedRows++; diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs index 81b7c4da1..bd74a46b7 100644 --- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs @@ -206,7 +206,9 @@ internal sealed class ExrEncoderCore 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 @@ internal sealed class ExrEncoderCore 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 22762289c..de1988674 100644 --- a/src/ImageSharp/Formats/Exr/ExrMetadata.cs +++ b/src/ImageSharp/Formats/Exr/ExrMetadata.cs @@ -53,7 +53,8 @@ public class ExrMetadata : IFormatMetadata 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 344c13cc9..d4a6be000 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -541,7 +541,7 @@ internal sealed class GifEncoderCore 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/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index d818aef1b..16c3cf116 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ internal class BlackIsZero16TiffColor : TiffBaseColorDecoder 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 ac316459d..730091b6a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder : TiffBaseColorDecoder : TiffBaseColorDecoder { 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 b422e65ad..0fb7057aa 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs @@ -109,7 +109,7 @@ internal class CieLab16PlanarTiffColor : TiffBasePlanarColorDecoder 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 @@ internal class CieLab16PlanarTiffColor : TiffBasePlanarColorDecoder 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 4455753bf..500914109 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs @@ -105,7 +105,7 @@ internal class CieLab16TiffColor : TiffBaseColorDecoder // 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 @@ internal class CieLab16TiffColor : TiffBaseColorDecoder // 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 1252f1d3d..b7ad55cb1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ internal class CieLab8PlanarTiffColor : TiffBasePlanarColorDecoder(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 ce5bed53c..7e71cab82 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs @@ -39,7 +39,7 @@ internal class CieLab8TiffColor : TiffBaseColorDecoder 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 509056267..d8e45c25c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs @@ -66,7 +66,7 @@ internal class CmykTiffColor : TiffBaseColorDecoder 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 @@ internal class CmykTiffColor : TiffBaseColorDecoder // 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 d1b519bdd..52315a02b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -97,7 +97,7 @@ internal class PaletteTiffColor : TiffBaseColorDecoder 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 @@ internal class PaletteTiffColor : TiffBaseColorDecoder 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 3a90e8174..f84a77939 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -30,7 +30,7 @@ internal class Rgb444TiffColor : TiffBaseColorDecoder 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 @@ internal class Rgb444TiffColor : TiffBaseColorDecoder 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 f9c17eb2f..f48879500 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -51,7 +51,7 @@ internal class RgbFloat323232TiffColor : TiffBaseColorDecoder 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 @@ internal class RgbFloat323232TiffColor : TiffBaseColorDecoder 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 a013abfbd..33f1703ae 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -63,7 +63,7 @@ internal class RgbPlanarTiffColor : TiffBasePlanarColorDecoder 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 3c205d147..0578cbc58 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -52,7 +52,7 @@ internal class RgbTiffColor : TiffBaseColorDecoder 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 086aa3b4e..015b31383 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 @@ internal class Rgba16161616TiffColor : TiffBaseColorDecoder 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 26ffbbab9..65bfe7431 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 @@ internal class Rgba8888TiffColor : TiffBaseColorDecoder { 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 87a019641..2784caa59 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 @@ internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder 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 @@ internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder 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 7a599a06a..c505b58d3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -80,11 +80,11 @@ internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder 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 68b59c95a..2c934036a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -63,16 +63,16 @@ internal class RgbaTiffColor : TiffBaseColorDecoder 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 dd36e912c..bb109de77 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -383,7 +383,7 @@ internal static class TiffColorDecoderFactory && 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 6986f25eb..7fe142d74 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -40,7 +40,7 @@ internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder : TiffBaseColorDecoder : TiffBaseColorDecoder { 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 b7d412f3c..3bc4500fd 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/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs index 2c84bcfc8..ad26eb4e6 100644 --- a/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs @@ -3,8 +3,7 @@ using System.Buffers; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Provides bulk operations for pixel formats that store associated alpha. /// /// The associated-alpha pixel format. -internal class AssociatedAlphaPixelOperations : PixelOperations +public abstract class AssociatedAlphaPixelOperations : PixelOperations where TPixel : unmanaged, IPixel { /// @@ -22,73 +21,49 @@ internal class AssociatedAlphaPixelOperations : PixelOperations => AssociatedAlphaPixelBlenders.GetPixelBlender(colorMode, alphaMode); /// - internal override Vector4 ToUnassociatedScaledVector4(TPixel source) - { - Vector4 vector = source.ToScaledVector4(); - Numerics.UnPremultiply(ref vector); - return vector; - } + protected abstract override void ToUnassociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); /// - internal override TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Associate(source)); + protected abstract override void ToAssociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); - /// - /// Converts an associated scaled vector to a destination pixel after associating RGB with the alpha value the destination stores. - /// - /// The associated scaled vector. - /// The destination pixel. - public virtual TPixel FromAssociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(Reassociate(source)); + /// + protected abstract override void FromUnassociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void FromAssociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); /// - internal override void ToUnassociatedScaledVector4( + protected abstract override void ToUnassociatedScaledVector4( Configuration configuration, ReadOnlySpan source, - Span destination) - { - this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); - Numerics.UnPremultiply(destination[..source.Length]); - } + Span destination); /// - internal override void ToAssociatedScaledVector4( + protected abstract override void ToAssociatedScaledVector4( Configuration configuration, ReadOnlySpan source, - Span destination) - => this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + Span destination); /// - internal override void FromUnassociatedScaledVector4( + protected abstract override void FromUnassociatedScaledVector4Destructive( Configuration configuration, Span source, - Span destination) - { - source = source[..destination.Length]; - - for (int i = 0; i < source.Length; i++) - { - source[i] = Associate(source[i]); - } + Span destination); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); - } - - /// - /// Converts associated scaled vectors to destination pixels after associating RGB with the alpha values the destination stores. - /// - /// The configuration. - /// The associated scaled vectors. - /// The destination pixels. - public virtual void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) - { - source = source[..destination.Length]; - - for (int i = 0; i < source.Length; i++) - { - source[i] = Reassociate(source[i]); - } - - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); - } + /// + protected abstract override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination); /// public override void From( @@ -96,11 +71,17 @@ internal class AssociatedAlphaPixelOperations : PixelOperations ReadOnlySpan source, Span destination) { - const int sliceLength = 1024; + 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(); + 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++) @@ -108,8 +89,13 @@ internal class AssociatedAlphaPixelOperations : PixelOperations int start = i * sliceLength; ReadOnlySpan sourceSlice = source.Slice(start, sliceLength); Span destinationSlice = destination.Slice(start, sliceLength); - PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); - this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); } int endOfCompleteSlices = numberOfSlices * sliceLength; @@ -118,10 +104,15 @@ internal class AssociatedAlphaPixelOperations : PixelOperations if (remainder > 0) { ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; - Span destinationSlice = destination[endOfCompleteSlices..]; + Span destinationSlice = destination.Slice(endOfCompleteSlices, remainder); vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); - this.FromUnassociatedScaledVector4(configuration, vectorSpan, destinationSlice); + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); } } @@ -131,7 +122,55 @@ internal class AssociatedAlphaPixelOperations : PixelOperations Span sourceVectors, Span destination, PixelConversionModifiers modifiers) - => base.FromVector4Destructive(configuration, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); + { + 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( @@ -139,7 +178,108 @@ internal class AssociatedAlphaPixelOperations : PixelOperations ReadOnlySpan source, Span destinationVectors, PixelConversionModifiers modifiers) - => base.ToVector4(configuration, source, destinationVectors, modifiers.Remove(PixelConversionModifiers.Premultiply)); + { + 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) @@ -209,50 +349,39 @@ internal class AssociatedAlphaPixelOperations : PixelOperations Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref TPixel sourceBase = ref MemoryMarshal.GetReference(source); - ref TDestinationPixel destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) + if (source.IsEmpty) { - Vector4 vector = this.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i)); - Unsafe.Add(ref destinationBase, i) = TDestinationPixel.FromScaledVector4(vector); + return; } - } - /// - /// Converts an unassociated scaled vector to the associated representation of the destination pixel type. - /// - /// The unassociated scaled vector. - /// The associated scaled vector. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Associate(Vector4 source) - { - // Round-trip alpha through TPixel so the generic fallback associates RGB with the value the destination actually stores. - source.W = TPixel.FromScaledVector4(new Vector4(0, 0, 0, source.W)).ToScaledVector4().W; - Numerics.Premultiply(ref source); - return source; - } + int sliceLength = Math.Min(source.Length, 1024); + int numberOfSlices = source.Length / sliceLength; - /// - /// Reassociates a scaled vector with the alpha value the destination pixel can store. - /// - /// The associated scaled vector. - /// The reassociated scaled vector. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Reassociate(Vector4 source) - { - float alpha = source.W; + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan()[..sliceLength]; + PixelOperations destinationOperations = PixelOperations.Instance; - if (alpha == 0) + // 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++) { - return Vector4.Zero; + 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); } - float storedAlpha = TPixel.FromScaledVector4(new Vector4(0, 0, 0, alpha)).ToScaledVector4().W; + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; - // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. - source *= storedAlpha / alpha; - source.W = storedAlpha; - return source; + 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 02936f160..473e2ed28 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; @@ -10,6 +11,19 @@ namespace SixLabors.ImageSharp.PixelFormats; /// internal static class HalfTypeHelper { + // 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 +39,256 @@ internal static class HalfTypeHelper /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static float Unpack(ushort value) => (float)BitConverter.UInt16BitsToHalf(value); + + /// + /// 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 528b3e76d..a4f6312dd 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 index 49c1e4709..94ae2ac71 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -30,7 +30,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -809,7 +809,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -1588,7 +1588,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -2367,7 +2367,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3146,7 +3146,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -3925,7 +3925,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -4704,7 +4704,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -5483,7 +5483,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -6262,7 +6262,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7041,7 +7041,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -7820,7 +7820,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -8599,7 +8599,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -9378,7 +9378,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10157,7 +10157,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -10936,7 +10936,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -11715,7 +11715,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -12494,7 +12494,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -13273,7 +13273,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14052,7 +14052,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -14831,7 +14831,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -15610,7 +15610,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -16389,7 +16389,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -17168,7 +17168,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -17947,7 +17947,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -18726,7 +18726,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -19505,7 +19505,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -20284,7 +20284,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -21063,7 +21063,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -21842,7 +21842,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -22621,7 +22621,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -23400,7 +23400,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24179,7 +24179,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -24958,7 +24958,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -25737,7 +25737,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -26516,7 +26516,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -27295,7 +27295,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28074,7 +28074,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -28853,7 +28853,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -29632,7 +29632,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -30411,7 +30411,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -31190,7 +31190,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -31969,7 +31969,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -32748,7 +32748,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -33527,7 +33527,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -34306,7 +34306,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -35085,7 +35085,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -35864,7 +35864,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -36643,7 +36643,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -37422,7 +37422,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -38201,7 +38201,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -38980,7 +38980,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -39759,7 +39759,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -40538,7 +40538,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -41317,7 +41317,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDest(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -42096,7 +42096,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -42875,7 +42875,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -43654,7 +43654,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -44433,7 +44433,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -45212,7 +45212,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -45991,7 +45991,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -46770,7 +46770,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -47549,7 +47549,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -48328,7 +48328,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -49107,7 +49107,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -49886,7 +49886,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -50665,7 +50665,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -51444,7 +51444,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -52223,7 +52223,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -53002,7 +53002,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -53781,7 +53781,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -54560,7 +54560,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -55339,7 +55339,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -56118,7 +56118,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -56897,7 +56897,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -57676,7 +57676,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -58455,7 +58455,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -59234,7 +59234,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -60013,7 +60013,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -60792,7 +60792,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -61571,7 +61571,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -62350,7 +62350,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -63129,7 +63129,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -63908,7 +63908,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -64687,7 +64687,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -65466,7 +65466,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -66245,7 +66245,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -67024,7 +67024,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -67803,7 +67803,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -68582,7 +68582,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -69361,7 +69361,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -70140,7 +70140,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -70919,7 +70919,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -71698,7 +71698,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -72477,7 +72477,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -73256,7 +73256,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -74035,7 +74035,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -74814,7 +74814,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -75593,7 +75593,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -76372,7 +76372,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightClear(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -77151,7 +77151,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.NormalXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -77930,7 +77930,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplyXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -78709,7 +78709,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.AddXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -79488,7 +79488,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.SubtractXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -80267,7 +80267,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -81046,7 +81046,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.DarkenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -81825,7 +81825,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.LightenXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -82604,7 +82604,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.OverlayXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// @@ -83383,7 +83383,7 @@ internal static partial class AssociatedAlphaPixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.HardLightXor(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt index b18f94a48..69426b729 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt @@ -73,7 +73,7 @@ var blenders = new []{ /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return FromBlendVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); } /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs index 64d0be696..9c71e97e5 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs @@ -12,8 +12,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; internal abstract class AssociatedAlphaPixelBlender : PixelBlender where TPixel : unmanaged, IPixel { - // Associated blenders are created only by AssociatedAlphaPixelOperations, so the cached cast establishes the format-specific output boundary once per closed pixel type. - private static readonly AssociatedAlphaPixelOperations Operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + private static readonly PixelOperations Operations = PixelOperations.Instance; /// protected override void ToBlendVector4( @@ -22,18 +21,11 @@ internal abstract class AssociatedAlphaPixelBlender : PixelBlender destination) { // Selecting the source representation once per row avoids a format check for every blended pixel. - PixelOperations.Instance.ToAssociatedScaledVector4(configuration, source, destination); + PixelOperations.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } /// - protected override Vector4 ToBlendVector4(TPixel source) => source.ToScaledVector4(); - - /// - /// Converts an associated blend result to the destination pixel representation. - /// - /// The associated blend result. - /// The destination pixel. - public static TPixel FromBlendVector4(Vector4 source) => Operations.FromAssociatedScaledVector4(source); + protected override Vector4 ToBlendVector4(TPixel source) => source.ToAssociatedScaledVector4(); /// protected override void FromBlendVector4( @@ -41,6 +33,6 @@ internal abstract class AssociatedAlphaPixelBlender : PixelBlender source, Span destination) { - Operations.FromAssociatedScaledVector4(configuration, source, destination); + Operations.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 05db95917..983508d2d 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -38,7 +38,7 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 @@ internal static class DefaultPixelBlenders /// 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 e20e47f2b..b1d8134ce 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 d32966c24..262370cd5 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -505,7 +505,7 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 @@ internal static partial class PorterDuffFunctions 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 7cb007bca..8837a90a3 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/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 47a18f54c..bb519c174 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -718,7 +718,7 @@ public abstract class PixelBlender ReadOnlySpan source, Span destination) where TPixelSource : unmanaged, IPixel - => PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, source, destination); + => PixelOperations.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); /// /// Converts a source pixel to the vector representation consumed by the blend functions. @@ -726,7 +726,7 @@ public abstract class PixelBlender /// The source pixel. /// The source vector. protected virtual Vector4 ToBlendVector4(TPixel source) - => PixelOperations.Instance.ToUnassociatedScaledVector4(source); + => source.ToUnassociatedScaledVector4(); /// /// Converts blend results from this blender's scaled-vector representation to destination pixels. @@ -738,7 +738,7 @@ public abstract class PixelBlender Configuration configuration, Span source, Span destination) - => PixelOperations.Instance.FromUnassociatedScaledVector4(configuration, source, 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 edc04fa7c..8cee6057d 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -6,32 +6,38 @@ using SixLabors.ImageSharp.ColorProfiles.Companding; 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 94fbf7eb7..65f70867c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -77,6 +77,38 @@ public partial struct A8 : IPixel, IPackedVector /// 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 ff95e714c..acd354b7f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -194,6 +194,47 @@ public partial struct Abgr32 : IPixel, IPackedVector /// 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 index 0d1730c80..9105a99f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Components are stored in alpha, blue, green, and red order from least to most significant byte. /// /// -/// Component, packed, and vector values use associated alpha representation. +/// The native component, packed, and vector representations use associated alpha. /// [StructLayout(LayoutKind.Sequential)] public partial struct Abgr32P : IPixel, IPackedVector @@ -147,9 +147,29 @@ public partial struct Abgr32P : IPixel, IPackedVector /// 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( @@ -162,10 +182,31 @@ public partial struct Abgr32P : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Abgr32P FromScaledVector4(Vector4 source) => Pack(source); + public static Abgr32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); /// - public static Abgr32P FromVector4(Vector4 source) => FromScaledVector4(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()); @@ -218,14 +259,6 @@ public partial struct Abgr32P : IPixel, IPackedVector /// public override readonly string ToString() => $"Abgr32P({this.R}, {this.G}, {this.B}, {this.A})"; - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static Abgr32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Abgr32P Pack(Vector4 vector) { @@ -233,6 +266,7 @@ public partial struct Abgr32P : IPixel, IPackedVector 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 7e40c2753..90ae88263 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -187,6 +187,47 @@ public partial struct Argb32 : IPixel, IPackedVector /// 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 index d5069638c..7c866c8fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Components are stored in alpha, red, green, and blue order from least to most significant byte. /// /// -/// Component, packed, and vector values use associated alpha representation. +/// The native component, packed, and vector representations use associated alpha. /// [StructLayout(LayoutKind.Sequential)] public partial struct Argb32P : IPixel, IPackedVector @@ -147,9 +147,29 @@ public partial struct Argb32P : IPixel, IPackedVector /// 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( @@ -162,10 +182,31 @@ public partial struct Argb32P : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Argb32P FromScaledVector4(Vector4 source) => Pack(source); + public static Argb32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); /// - public static Argb32P FromVector4(Vector4 source) => FromScaledVector4(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()); @@ -218,14 +259,6 @@ public partial struct Argb32P : IPixel, IPackedVector /// public override readonly string ToString() => $"Argb32P({this.R}, {this.G}, {this.B}, {this.A})"; - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static Argb32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Argb32P Pack(Vector4 vector) { @@ -233,6 +266,7 @@ public partial struct Argb32P : IPixel, IPackedVector 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 944cdf7bf..c64047518 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -97,6 +97,48 @@ public partial struct Bgr24 : IPixel /// 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 87055bf22..a77174a71 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -75,6 +75,48 @@ public partial struct Bgr565(Vector3 vector) : IPixel, IPackedVector 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 8c47032e7..e8405dd2b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -136,6 +136,47 @@ public partial struct Bgra32 : IPixel, IPackedVector /// 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 index bc050d148..6ce3138f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Components are stored in blue, green, red, and alpha order from least to most significant byte. /// /// -/// Component, packed, and vector values use associated alpha representation. +/// The native component, packed, and vector representations use associated alpha. /// [StructLayout(LayoutKind.Sequential)] public partial struct Bgra32P : IPixel, IPackedVector @@ -147,9 +147,29 @@ public partial struct Bgra32P : IPixel, IPackedVector /// 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( @@ -162,10 +182,31 @@ public partial struct Bgra32P : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Bgra32P FromScaledVector4(Vector4 source) => Pack(source); + public static Bgra32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); /// - public static Bgra32P FromVector4(Vector4 source) => FromScaledVector4(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()); @@ -218,14 +259,6 @@ public partial struct Bgra32P : IPixel, IPackedVector /// public override readonly string ToString() => $"Bgra32P({this.R}, {this.G}, {this.B}, {this.A})"; - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static Bgra32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Bgra32P Pack(Vector4 vector) { @@ -233,6 +266,7 @@ public partial struct Bgra32P : IPixel, IPackedVector 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 55971210c..97c2eabd0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -88,6 +88,47 @@ public partial struct Bgra4444 : IPixel, IPackedVector /// 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 4c94dea5f..799c2ce5c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -86,6 +86,47 @@ public partial struct Bgra5551 : IPixel, IPackedVector /// 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 680a7ee0b..d8e9ca9ec 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -88,6 +88,58 @@ public partial struct Byte4 : IPixel, IPackedVector /// 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 00deadb12..c1204efed 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -72,6 +72,48 @@ public partial struct HalfSingle : IPixel, IPackedVector /// 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 HalfSingle FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle 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 HalfSingle FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromAssociatedVector4(Vector4 source) + { + // Only the stored color component uses the native [-1, 1] encoding; W remains the normalized source alpha. + source.X = (source.X + 1F) / 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfSingle FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 03d4dee89..900ca07b4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -84,6 +84,49 @@ public partial struct HalfVector2 : IPixel, IPackedVector /// 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) + { + // 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 HalfVector2 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index d0b57d788..dec77f048 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -89,6 +89,61 @@ public partial struct HalfVector4 : IPixel, IPackedVector /// 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 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) + { + // 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 HalfVector4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs index 7b3bda330..4a51c21af 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs @@ -3,7 +3,6 @@ using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats; @@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Packed pixel type containing four associated 16-bit floating-point values. /// /// -/// Packed and vector values use associated alpha representation. +/// The native packed and vector representations use associated alpha. /// public partial struct HalfVector4P : IPixel, IPackedVector { @@ -55,9 +54,7 @@ public partial struct HalfVector4P : IPixel, IPackedVector /// public readonly Rgba32 ToRgba32() { - Vector4 vector = this.ToScaledVector4(); - Numerics.UnPremultiply(ref vector); - return Rgba32.FromScaledVector4(vector); + return Rgba32.FromScaledVector4(this.ToUnassociatedScaledVector4()); } /// @@ -69,6 +66,19 @@ public partial struct HalfVector4P : IPixel, IPackedVector return scaled; } + /// + [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), @@ -76,6 +86,21 @@ public partial struct HalfVector4P : IPixel, IPackedVector HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + // Association is defined in the common scaled domain. Binary16-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( @@ -87,15 +112,52 @@ public partial struct HalfVector4P : IPixel, IPackedVector public static PixelOperations CreatePixelOperations() => new PixelOperations(); /// - public static HalfVector4P FromScaledVector4(Vector4 source) + 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) + { + // Convert the binary16-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 HalfVector4P FromAssociatedVector4(Vector4 source) { - source *= 2F; - source -= Vector4.One; - return FromVector4(source); + // Reassociation must also operate in scaled space so RGB follows the quantized scaled alpha rather than the binary16-native W component. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); } /// - public static HalfVector4P FromVector4(Vector4 source) => new() { PackedValue = Pack(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)); + + /// + /// Packs an associated scaled vector into binary16-native storage. + /// + /// The associated scaled vector. + /// The packed pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static HalfVector4P PackAssociatedScaledVector4(Vector4 source) + { + // Binary16 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 HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); @@ -152,14 +214,6 @@ public partial struct HalfVector4P : IPixel, IPackedVector return FormattableString.Invariant($"HalfVector4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); } - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static HalfVector4P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Associate(source)); - /// /// Converts an unassociated scaled vector to the associated representation of a half-precision destination. /// @@ -168,6 +222,8 @@ public partial struct HalfVector4P : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 Associate(Vector4 source) { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + // Quantize alpha through the destination's native half representation before RGB is associated with it. float nativeAlpha = (source.W * 2F) - 1F; source.W = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; @@ -175,20 +231,6 @@ public partial struct HalfVector4P : IPixel, IPackedVector return source; } - /// - /// Converts unassociated scaled vectors to the associated representation of a half-precision destination. - /// - /// The vectors to convert in place. - private static void Associate(Span source) - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); - } - } - /// /// Reassociates a scaled vector with the alpha value the destination stores. /// @@ -199,34 +241,22 @@ public partial struct HalfVector4P : IPixel, IPackedVector { float alpha = source.W; - if (alpha == 0) + if (alpha <= 0) { return Vector4.Zero; } - float nativeAlpha = (alpha * 2F) - 1F; + // The binary16-native alpha range is [-1, 1]. Clamp before packing so out-of-range scaled alpha cannot escape the pixel's declared [0, 1] opacity range. + float nativeAlpha = Numerics.Clamp((alpha * 2F) - 1F, -1F, 1F); float storedAlpha = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. source *= storedAlpha / alpha; source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); return source; } - /// - /// Reassociates scaled vectors with the alpha values the destination stores. - /// - /// The vectors to convert in place. - private static void Reassociate(Span source) - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); - } - } - /// /// Packs native half-precision components into a 64-bit value. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index c5893f770..1ab8e9650 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -77,6 +77,48 @@ public partial struct L16 : IPixel, IPackedVector /// 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 008eed459..9908e0306 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -79,6 +79,48 @@ public partial struct L8 : IPixel, IPackedVector /// 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 6c3fa7829..3a15f24d5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -106,6 +106,47 @@ public partial struct La16 : IPixel, IPackedVector /// 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 c99f6fe60..ae3190d96 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -103,6 +103,47 @@ public partial struct La32 : IPixel, IPackedVector /// 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 da2ab2440..317e59574 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -87,6 +87,49 @@ public partial struct NormalizedByte2 : IPixel, IPackedVector 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) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 1fb386725..09ddd0e91 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.PixelFormats; 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 MinusOne = Vector128.Create(-1f).AsVector4(); @@ -70,10 +71,14 @@ public partial struct NormalizedByte4 : IPixel, IPackedVector> 0) & 0xFF) + MaxPos, + (sbyte)((this.PackedValue >> 8) & 0xFF) + MaxPos, + (sbyte)((this.PackedValue >> 16) & 0xFF) + MaxPos, + (sbyte)((this.PackedValue >> 24) & 0xFF) + MaxPos); + + return scaled / ScaledMagnitude; } /// @@ -94,6 +99,61 @@ public partial struct NormalizedByte4 : IPixel, IPackedVector 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 index b6549b7dd..4f6df4c6f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -3,7 +3,6 @@ using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; @@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1. /// /// -/// Packed and vector values use associated alpha representation. +/// The native packed and vector representations use associated alpha. /// public partial struct NormalizedByte4P : IPixel, IPackedVector { @@ -64,12 +63,24 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector /// 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) + MaxPos, + (sbyte)((this.PackedValue >> 8) & 0xFF) + MaxPos, + (sbyte)((this.PackedValue >> 16) & 0xFF) + MaxPos, + (sbyte)((this.PackedValue >> 24) & 0xFF) + MaxPos); + + return scaled / ScaledMagnitude; } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => ToUnassociatedScaledVector4(this); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + /// public readonly Vector4 ToVector4() => new( (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, @@ -77,6 +88,21 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector (sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, (sbyte)((this.PackedValue >> 24) & 0xFF) / 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( @@ -88,15 +114,52 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector public static PixelOperations CreatePixelOperations() => new PixelOperations(); /// - public static NormalizedByte4P FromScaledVector4(Vector4 source) + 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) { - source *= 2F; - source -= Vector4.One; - return FromVector4(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); } /// - public static NormalizedByte4P FromVector4(Vector4 source) => new() { PackedValue = Pack(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()); @@ -153,13 +216,6 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector return FormattableString.Invariant($"NormalizedByte4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); } - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(Associate(source)); - /// /// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination. /// @@ -168,6 +224,8 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector [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); @@ -202,20 +260,6 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector return vector; } - /// - /// Converts unassociated scaled vectors to the associated representation of a signed-normalized-byte destination. - /// - /// The vectors to convert in place. - private static void Associate(Span source) - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, i)); - } - } - /// /// Reassociates a scaled vector with the alpha value the destination stores. /// @@ -226,7 +270,7 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector { float alpha = source.W; - if (alpha == 0) + if (alpha <= 0) { return Vector4.Zero; } @@ -237,23 +281,10 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector // 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; } - /// - /// Reassociates scaled vectors with the alpha values the destination stores. - /// - /// The vectors to convert in place. - private static void Reassociate(Span source) - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Unsafe.Add(ref sourceBase, i) = Reassociate(Unsafe.Add(ref sourceBase, i)); - } - } - /// /// Packs native signed normalized components into a 32-bit value. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 0942c3a76..dfc42594d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -89,6 +89,49 @@ public partial struct NormalizedShort2 : IPixel, IPackedVector /// 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) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 2b33fec27..a5ab585df 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -17,6 +17,7 @@ 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,10 +72,14 @@ public partial struct NormalizedShort4 : IPixel, IPackedVector [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) + MaxPos, + (short)((this.PackedValue >> 0x10) & 0xFFFF) + MaxPos, + (short)((this.PackedValue >> 0x20) & 0xFFFF) + MaxPos, + (short)((this.PackedValue >> 0x30) & 0xFFFF) + MaxPos); + + return scaled / ScaledMagnitude; } /// @@ -95,6 +100,61 @@ public partial struct NormalizedShort4 : IPixel, IPackedVector /// 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 da0e6ec60..da6762912 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 index 0888130f7..746fbf85f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs @@ -17,50 +17,48 @@ public partial struct Abgr32P internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(Abgr32P source) - => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override Abgr32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); /// - public override Abgr32P FromAssociatedScaledVector4(Vector4 source) - => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source); + 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); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); } /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); } /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + 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 index 472f22dc6..80dbd3c31 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs @@ -17,50 +17,48 @@ public partial struct Argb32P internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(Argb32P source) - => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override Argb32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); /// - public override Argb32P FromAssociatedScaledVector4(Vector4 source) - => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source); + 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); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); } /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); } /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + 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 87175fe21..737f6999f 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 index f81493469..606990d66 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs @@ -17,50 +17,48 @@ public partial struct Bgra32P internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(Bgra32P source) - => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override Bgra32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); /// - public override Bgra32P FromAssociatedScaledVector4(Vector4 source) - => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source); + 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); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); } /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); } /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + 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 c012547c9..7f0919919 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/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs index 770f3a1de..ec1fe3dd3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.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 HalfSingle /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(1F, 0F, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(2F, 1F, 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 uses the affine native range; W must remain normalized so the scaled path can unassociate the source color. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs index 160ab9bd0..3f46d61f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.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 HalfVector2 /// /// 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. W is the normalized alpha used by the scaled unassociation step. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs index 703138454..e29055366 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,89 @@ public partial struct HalfVector4 /// /// 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)); + + HalfVector4P.PixelOperations.Unpack(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]; + + // HalfVector4P has the same four-half layout, allowing both formats to share the SIMD half expansion. Association must + // still occur in scaled space because the native half values use an affine [-1, 1] color coordinate system. + HalfVector4P.PixelOperations.Unpack(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)); + + HalfVector4P.PixelOperations.Unpack(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]; + HalfVector4P.PixelOperations.Unpack(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]; + HalfVector4P.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]; + + // Normalize native alpha with RGB before unassociation, then reuse the shared half packing kernel in scaled mode. + Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + Numerics.UnPremultiply(source); + HalfVector4P.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]; + HalfVector4P.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); + HalfVector4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs index 39e948fe1..1646dd4d3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs @@ -2,56 +2,759 @@ // 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 bulk operations. +/// Provides optimized overrides for bulk operations. /// public partial struct HalfVector4P { /// - /// Provides bulk operations for . + /// Provides optimized bulk operations for . /// internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(HalfVector4P source) + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - Vector4 vector = source.ToScaledVector4(); - Numerics.UnPremultiply(ref vector); - return vector; + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + UnpackUnassociated(source, destination[..source.Length], false); } /// - internal override HalfVector4P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Unpack(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]; + PackUnassociated(source, destination, false); + } /// - public override HalfVector4P FromAssociatedScaledVector4(Vector4 source) - => FromScaledVector4(Reassociate(source)); + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PackAssociated(source, destination, false); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); - Numerics.UnPremultiply(destination[..source.Length]); + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + UnpackUnassociated(source, destination[..source.Length], true); } /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - source = source[..destination.Length]; - Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Unpack(source, destination[..source.Length], true); } /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PackUnassociated(source, destination, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PackAssociated(source, destination, true); + } + + /// + /// Unpacks native or scaled binary16 components into vectors. + /// + /// The packed source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + internal static void Unpack(ReadOnlySpan source, Span destination, bool scaled) + { + 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); + + if (scaled) + { + lower = ToScaled(lower); + upper = ToScaled(upper); + } + + 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); + + if (scaled) + { + lower = ToScaled(lower); + upper = ToScaled(upper); + } + + 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); + + if (scaled) + { + lower = ToScaled(lower); + upper = ToScaled(upper); + } + + Vector128.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // A pixel contains four halves, 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 vector = HalfTypeHelper.Unpack(packed).Lower; + + if (scaled) + { + vector = ToScaled(vector); + } + + Vector128.StoreUnsafe(vector, ref destinationBase, (nuint)i); + } + + return; + } + + ref HalfVector4P pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + HalfVector4P pixel = Unsafe.Add(ref pixelBase, (uint)pixelIndex); + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = scaled ? pixel.ToScaledVector4() : pixel.ToVector4(); + } + } + + /// + /// Unpacks associated binary16 storage directly into unassociated native or scaled vectors. + /// + /// The packed source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + private static void UnpackUnassociated(ReadOnlySpan source, Span destination, bool scaled) + { + 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(ToUnassociated(upper, scaled), 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(ToUnassociated(upper, scaled), 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(ToUnassociated(upper, scaled), ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // A pixel contains four halves, 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 vector = HalfTypeHelper.Unpack(packed).Lower; + Vector128.StoreUnsafe(ToUnassociated(vector, scaled), ref destinationBase, (nuint)i); + } + + return; + } + + ref HalfVector4P pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + HalfVector4P pixel = Unsafe.Add(ref pixelBase, (uint)pixelIndex); + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = scaled ? pixel.ToUnassociatedScaledVector4() : pixel.ToUnassociatedVector4(); + } + } + + /// + /// Packs native or scaled vectors into binary16 storage. + /// + /// The source vectors. + /// The destination pixels. + /// Whether the source contains scaled vectors. + internal static void Pack(Span source, Span destination, bool scaled) + { + 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 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); + Vector512.StoreUnsafe(packed, 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 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); + Vector256.StoreUnsafe(packed, 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 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); + Vector128.StoreUnsafe(packed, ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. + Vector128 vector = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + Vector128 packed = scaled ? PackScaled(vector, vector) : 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 HalfVector4P 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) = scaled ? FromScaledVector4(vector) : FromVector4(vector); + } + } + + /// + /// Associates unassociated native or scaled vectors and packs them into binary16 storage in one pass. + /// + /// The unassociated vectors. + /// The destination pixels. + /// Whether the source contains scaled vectors. + private static void PackUnassociated(Span source, Span destination, bool scaled) + { + 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; + + // Quantize alpha to the binary16 value the destination will store before associating RGB. The kernels replace W separately + // so alpha is stored unchanged instead of being multiplied by itself. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = Associate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector512 upper = Associate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)), scaled); + Vector512.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = Associate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector256 upper = Associate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)), scaled); + Vector256.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector128 upper = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)), scaled); + Vector128.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. + Vector128 vector = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector128 packed = PackScaled(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 HalfVector4P pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + + // Qualify the containing pixel type because this nested class also declares bulk overloads with these names. + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = scaled ? HalfVector4P.FromUnassociatedScaledVector4(vector) : HalfVector4P.FromUnassociatedVector4(vector); + } + } + + /// + /// Reassociates associated native or scaled vectors and packs them into binary16 storage in one pass. + /// + /// The associated vectors. + /// The destination pixels. + /// Whether the source contains scaled vectors. + private static void PackAssociated(Span source, Span destination, bool scaled) + { + 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; + + // Preserve straight color across binary16 alpha quantization by scaling RGB by storedAlpha / inputAlpha. The kernels replace + // W, clamp RGB to stored alpha, and clear zero-alpha vectors so the packed result remains valid associated color. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = Reassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector512 upper = Reassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)), scaled); + Vector512.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = Reassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector256 upper = Reassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)), scaled); + Vector256.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector128 upper = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)), scaled); + Vector128.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. + Vector128 vector = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); + Vector128 packed = PackScaled(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 HalfVector4P pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + + // Qualify the containing pixel type because this nested class also declares bulk overloads with these names. + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = scaled ? HalfVector4P.FromAssociatedScaledVector4(vector) : HalfVector4P.FromAssociatedVector4(vector); + } + } + + /// + /// Converts native binary16 values to the normalized range used by pixel conversions. + /// + /// The native values. + /// The scaled values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToScaled(Vector128 source) => (source + Vector128.One) / Vector128.Create(2F); + + /// + /// Converts native binary16 values to the normalized range used by pixel conversions. + /// + /// The native values. + /// The scaled values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToScaled(Vector256 source) => (source + Vector256.One) / Vector256.Create(2F); + + /// + /// Converts native binary16 values to the normalized range used by pixel conversions. + /// + /// The native values. + /// The scaled values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToScaled(Vector512 source) => (source + Vector512.One) / Vector512.Create(2F); + + /// + /// Converts associated native binary16 values to unassociated native or scaled vectors. + /// + /// The associated native values. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToUnassociated(Vector128 source, bool scaled) + { + source = ToScaled(source); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 result = Vector128.ConditionalSelect(Vector128.Equals(alpha, Vector128.Zero), source, source / alpha); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + result = Vector128.ConditionalSelect(alphaMask, alpha, result); + + // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. + return scaled ? result : (result * Vector128.Create(2F)) - Vector128.One; + } + + /// + /// Converts associated native binary16 values to unassociated native or scaled vectors. + /// + /// The associated native values. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToUnassociated(Vector256 source, bool scaled) + { + source = ToScaled(source); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 result = Vector256.ConditionalSelect(Vector256.Equals(alpha, Vector256.Zero), source, source / alpha); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector256.ConditionalSelect(alphaMask, alpha, result); + + // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. + return scaled ? result : (result * Vector256.Create(2F)) - Vector256.One; + } + + /// + /// Converts associated native binary16 values to unassociated native or scaled vectors. + /// + /// The associated native values. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToUnassociated(Vector512 source, bool scaled) + { + source = ToScaled(source); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 result = Vector512.ConditionalSelect(Vector512.Equals(alpha, Vector512.Zero), source, source / 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, alpha, result); + + // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. + return scaled ? result : (result * Vector512.Create(2F)) - Vector512.One; + } + + /// + /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = ToScaled(source); + } + + source = Vector128.Min(Vector128.Max(source, zero), one); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector128.Create(2F)) - one) + one) / Vector128.Create(2F); + Vector128 result = source * storedAlpha; + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = ToScaled(source); + } + + source = Vector256.Min(Vector256.Max(source, zero), one); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector256.Create(2F)) - one) + one) / Vector256.Create(2F); + Vector256 result = source * storedAlpha; + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = ToScaled(source); + } + + source = Vector512.Min(Vector512.Max(source, zero), one); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector512.Create(2F)) - one) + one) / Vector512.Create(2F); + 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 native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Reassociate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = ToScaled(source); + } + + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 nativeAlpha = (alpha * Vector128.Create(2F)) - one; + Vector128 clampedNativeAlpha = Vector128.Min(Vector128.Max(nativeAlpha, -one), one); + + // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. + nativeAlpha = Vector128.ConditionalSelect(Vector128.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); + + // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. + Vector128 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector128.Create(2F); + 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); + } + + /// + /// Reassociates native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Reassociate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = ToScaled(source); + } + + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 nativeAlpha = (alpha * Vector256.Create(2F)) - one; + Vector256 clampedNativeAlpha = Vector256.Min(Vector256.Max(nativeAlpha, -one), one); + + // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. + nativeAlpha = Vector256.ConditionalSelect(Vector256.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); + + // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. + Vector256 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector256.Create(2F); + 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 native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Reassociate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = ToScaled(source); + } + + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 nativeAlpha = (alpha * Vector512.Create(2F)) - one; + Vector512 clampedNativeAlpha = Vector512.Min(Vector512.Max(nativeAlpha, -one), one); + + // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. + nativeAlpha = Vector512.ConditionalSelect(Vector512.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); + + // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. + Vector512 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector512.Create(2F); + 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); + } + + /// + /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// + /// The lower scaled values. + /// The upper scaled values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 PackScaled(Vector128 lower, Vector128 upper) + { + lower *= Vector128.Create(2F); + lower -= Vector128.One; + upper *= Vector128.Create(2F); + upper -= Vector128.One; + return HalfTypeHelper.Pack(lower, upper); + } + + /// + /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// + /// The lower scaled values. + /// The upper scaled values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 PackScaled(Vector256 lower, Vector256 upper) + { + lower *= Vector256.Create(2F); + lower -= Vector256.One; + upper *= Vector256.Create(2F); + upper -= Vector256.One; + return HalfTypeHelper.Pack(lower, upper); + } + + /// + /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// + /// The lower scaled values. + /// The upper scaled values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 PackScaled(Vector512 lower, Vector512 upper) { - source = source[..destination.Length]; - Reassociate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + lower *= Vector512.Create(2F); + lower -= Vector512.One; + upper *= Vector512.Create(2F); + upper -= Vector512.One; + return HalfTypeHelper.Pack(lower, upper); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs index c9714c217..b79d2a5b2 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 e7b463fe0..fb7945fdf 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 36f4a0bf5..5568fc742 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 e67321e4f..d467b802e 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 index e8a9b875a..509803f50 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs @@ -4,59 +4,773 @@ 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 bulk operations. +/// Provides optimized overrides for bulk operations. /// public partial struct NormalizedByte4P { /// - /// Provides bulk operations for . + /// Provides optimized bulk operations for . /// internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source) - => NormalizedByte4P.ToUnassociatedScaledVector4(source); + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(source, destination[..source.Length], false); + } /// - internal override NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToVector4(source, destination[..source.Length], false); + } /// - public override NormalizedByte4P FromAssociatedScaledVector4(Vector4 source) - => FromScaledVector4(Reassociate(source)); + 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); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref NormalizedByte4P sourceBase = ref MemoryMarshal.GetReference(source); + 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; + + if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector512 integers = Avx512F.ConvertToVector512Int32(packed); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Avx512F.ConvertToVector512Single(integers), scaled); + } + } + + if (Avx2.IsSupported) + { + 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))); + Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Avx.ConvertToVector256Single(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) + { + 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) + { + 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) + { + 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; + + if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector512 integers = Avx512F.ConvertToVector512Int32(packed); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx512F.ConvertToVector512Single(integers), scaled); + } + } + + if (Avx2.IsSupported) + { + 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))); + Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx.ConvertToVector256Single(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) + { + 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) + { + 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) + { + 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++) { - Unsafe.Add(ref destinationBase, i) = NormalizedByte4P.ToUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, 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); } } - /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + /// + /// 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) { - source = source[..destination.Length]; - Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + 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); } - /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + /// + /// 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) { - source = source[..destination.Length]; - Reassociate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + 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 && Avx512F.IsSupported) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector512 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector128 packed = Avx512F.ConvertToVector128SByteWithSaturation(ConvertToPackedInt32(vector, scaled)); + 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 99636cb37..f35f2a2da 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 ad6aa8d8a..8a60d68b4 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 7bca1c781..5be973404 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 c9ed730c4..31816ceb0 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 000000000..990fd64bd --- /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 index 7f7386f1d..19338f12f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs @@ -17,50 +17,48 @@ public partial struct Rgba32P internal class PixelOperations : AssociatedAlphaPixelOperations { /// - internal override Vector4 ToUnassociatedScaledVector4(Rgba32P source) - => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source.R, source.G, source.B, source.A); + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override Rgba32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); /// - public override Rgba32P FromAssociatedScaledVector4(Vector4 source) - => Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source); + 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); + } /// - internal override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); /// - internal override void FromUnassociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.Associate(source); - this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); } /// - public override void FromAssociatedScaledVector4(Configuration configuration, Span source, Span destination) + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) { - source = source[..destination.Length]; - Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); } /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan source, - Span destinationVectors, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.ToVector4(source, destinationVectors, modifiers); - - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destination, - PixelConversionModifiers modifiers) - => Vector4Converters.AssociatedRgbaCompatible.FromVector4(sourceVectors, destination, modifiers); + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index 077164da7..d4b868871 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -23,9 +23,13 @@ public partial struct RgbaVector ReadOnlySpan sourcePixels, Span destinationPixels) { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + destinationPixels = destinationPixels[..sourcePixels.Length]; Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, sourcePixels, destinationVectors); + // 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); @@ -41,7 +45,11 @@ public partial struct RgbaVector 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]); } /// @@ -53,6 +61,7 @@ public partial struct RgbaVector { 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 d8225f18a..7520b33e0 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(MaxPos, MaxPos, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(MaxPos * 2F, MaxPos * 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) + { + // 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, NativeDivisor); + 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 3d7043b0c..d9108be41 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(MaxPos); + private static readonly Vector4 NativeMagnitude = new(MaxPos * 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], 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 e7c97269e..b84e0bf78 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -84,6 +84,48 @@ public partial struct Rg32 : IPixel, IPackedVector /// 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 190407296..b8c2d2098 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -108,6 +108,48 @@ public partial struct Rgb24 : IPixel /// 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 ed6f57abb..0a240b369 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -91,6 +91,48 @@ public partial struct Rgb48 : IPixel /// 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 12275061d..4b940a6a5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -91,7 +91,49 @@ public partial struct Rgb96 : IPixel, IEquatable /// [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 cdee22964..79913b499 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -86,6 +86,47 @@ public partial struct Rgba1010102 : IPixel, IPackedVector /// 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 7fc62b7ff..86fab4f61 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -95,6 +95,47 @@ public partial struct Rgba128 : IPixel, IEquatable /// 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 0cec0b30d..689d31793 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -232,6 +232,47 @@ public partial struct Rgba32 : IPixel, IPackedVector /// 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 index 6bf2e1db0..c7bb17ce8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Components are stored in red, green, blue, and alpha order from least to most significant byte. /// /// -/// Component, packed, and vector values use associated alpha representation. +/// The native component, packed, and vector representations use associated alpha. /// [StructLayout(LayoutKind.Sequential)] public partial struct Rgba32P : IPixel, IPackedVector @@ -147,9 +147,29 @@ public partial struct Rgba32P : IPixel, IPackedVector /// 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( @@ -162,10 +182,31 @@ public partial struct Rgba32P : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32P FromScaledVector4(Vector4 source) => Pack(source); + public static Rgba32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); /// - public static Rgba32P FromVector4(Vector4 source) => FromScaledVector4(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()); @@ -218,14 +259,6 @@ public partial struct Rgba32P : IPixel, IPackedVector /// public override readonly string ToString() => $"Rgba32P({this.R}, {this.G}, {this.B}, {this.A})"; - /// - /// Converts an unassociated scaled vector to associated representation. - /// - /// The unassociated scaled vector. - /// The associated pixel. - private static Rgba32P FromUnassociatedScaledVector4(Vector4 source) - => FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.Associate(source)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Rgba32P Pack(Vector4 vector) { @@ -233,6 +266,7 @@ public partial struct Rgba32P : IPixel, IPackedVector 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 c73daaf86..a9bc1e062 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -212,6 +212,47 @@ public partial struct Rgba64 : IPixel, IPackedVector /// 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/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 3f9cab32f..b1704d526 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -106,6 +106,47 @@ public partial struct RgbaVector : IPixel /// 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 39c853a5e..59c6b4e0a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -92,6 +92,49 @@ public partial struct Short2 : IPixel, IPackedVector /// 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 normalized source alpha. + source.X = (source.X + MaxPos) / (MaxPos * 2F); + source.Y = (source.Y + MaxPos) / (MaxPos * 2F); + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short2 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index b6cece2be..a128668fd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -99,6 +99,61 @@ public partial struct Short4 : IPixel, IPackedVector /// 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 signed encoding, so direct multiplication would use the wrong zero point. + vector *= MaxPos * 2F; + vector -= new Vector4(MaxPos); + 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 affine signed native encoding to logical [0, 1] space before unassociating. + source += new Vector4(MaxPos); + source /= MaxPos * 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 712d43f76..854b515dc 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -57,7 +57,7 @@ public partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 partial class PixelOperations 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 b2697cb4e..769f3b44b 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 08a3044b7..87957a995 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -5,14 +5,19 @@ using System.Buffers; 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 @@ -27,18 +32,22 @@ public partial class PixelOperations #pragma warning restore CA1000 // Do not declare static members on generic types /// - /// Converts a pixel to the unassociated scaled vector representation used by color operations. + /// Converts pixels to unassociated vectors in the pixel format's native numeric range. /// - /// The source pixel. - /// The unassociated scaled vector. - internal virtual Vector4 ToUnassociatedScaledVector4(TPixel source) => source.ToScaledVector4(); + /// 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 an unassociated scaled vector to a pixel. + /// Converts pixels to associated vectors in the pixel format's native numeric range. /// - /// The unassociated scaled vector. - /// The converted pixel. - internal virtual TPixel FromUnassociatedScaledVector4(Vector4 source) => TPixel.FromScaledVector4(source); + /// 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. @@ -46,11 +55,11 @@ public partial class PixelOperations /// The configuration. /// The source pixels. /// The destination vectors. - internal virtual void ToUnassociatedScaledVector4( + protected virtual void ToUnassociatedScaledVector4( Configuration configuration, ReadOnlySpan source, Span destination) - => this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); /// /// Converts pixels to associated scaled vectors. @@ -58,26 +67,53 @@ public partial class PixelOperations /// The configuration. /// The source pixels. /// The destination vectors. - internal virtual void ToAssociatedScaledVector4( + protected virtual void ToAssociatedScaledVector4( Configuration configuration, ReadOnlySpan source, Span destination) - { - this.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale); - Numerics.Premultiply(destination[..source.Length]); - } + => 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. + /// 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. - internal virtual void FromUnassociatedScaledVector4( + protected virtual void FromAssociatedScaledVector4Destructive( Configuration configuration, Span source, Span destination) - => this.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale); + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); /// /// Gets the pixel type info for the given . @@ -86,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, @@ -104,20 +139,63 @@ public partial class PixelOperations PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + 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); + } - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destination, modifiers); + 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, @@ -126,12 +204,12 @@ public partial class PixelOperations => 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, @@ -139,8 +217,53 @@ public partial class PixelOperations 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); + + 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; + } - Utils.Vector4Converters.Default.ToVector4(source, destinationVectors, modifiers); + 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); + } } /// @@ -156,10 +279,10 @@ public partial class PixelOperations => 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. @@ -180,8 +303,8 @@ public partial class PixelOperations int start = i * sliceLength; ReadOnlySpan s = source.Slice(start, sliceLength); Span d = destination.Slice(start, sliceLength); - PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan); - 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; @@ -191,8 +314,8 @@ public partial class PixelOperations ReadOnlySpan s = source[endOfCompleteSlices..]; Span d = destination[endOfCompleteSlices..]; vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToUnassociatedScaledVector4(configuration, s, vectorSpan); - 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); } } diff --git a/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs new file mode 100644 index 000000000..154d64389 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs @@ -0,0 +1,400 @@ +// 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 ShortMagnitude = ShortMaximum * 2F; + + /// + /// 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) + { + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + vector += new Vector4(ShortMaximum); + vector /= ShortMagnitude; + } + else + { + vector /= ShortMaximum; + } + } + else if (scaled) + { + vector += new Vector4(ShortMaximum); + vector /= ShortMagnitude; + } + + 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 *= ShortMagnitude; + vector -= new Vector4(ShortMaximum); + } + + 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) + { + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector512.Create(ShortMaximum); + source /= Vector512.Create(ShortMagnitude); + } + else + { + source /= Vector512.Create(ShortMaximum); + } + } + else if (scaled) + { + source += Vector512.Create(ShortMaximum); + source /= Vector512.Create(ShortMagnitude); + } + + 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) + { + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector256.Create(ShortMaximum); + source /= Vector256.Create(ShortMagnitude); + } + else + { + source /= Vector256.Create(ShortMaximum); + } + } + else if (scaled) + { + source += Vector256.Create(ShortMaximum); + source /= Vector256.Create(ShortMagnitude); + } + + 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) + { + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector128.Create(ShortMaximum); + source /= Vector128.Create(ShortMagnitude); + } + else + { + source /= Vector128.Create(ShortMaximum); + } + } + else if (scaled) + { + source += Vector128.Create(ShortMaximum); + source /= Vector128.Create(ShortMagnitude); + } + + 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(ShortMagnitude); + source -= Vector512.Create(ShortMaximum); + } + + 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(ShortMagnitude); + source -= Vector256.Create(ShortMaximum); + } + + 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(ShortMagnitude); + source -= Vector128.Create(ShortMaximum); + } + + 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 000000000..0096085e9 --- /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 index 7ab839cc5..dd38e4118 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -33,48 +33,322 @@ internal static partial class Vector4Converters { // 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; + + if (Avx512F.IsSupported) + { + int pixelsPerVector = Vector512.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)); + Vector512 integers = Avx512F.ConvertToVector512Int32(packed); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx512F.ConvertToVector512Single(integers)); + } + } + + if (Avx2.IsSupported) + { + int pixelsPerVector = Vector256.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsByte()); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx.ConvertToVector256Single(integers)); + } + } - if (alpha == 0) + if (Vector128.IsHardwareAccelerated) { - // Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case. - return vector / byte.MaxValue; + 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(); + } } - Numerics.UnPremultiply(ref vector); - vector.W /= byte.MaxValue; - return vector; + 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 an unassociated scaled vector to the associated representation of an unsigned-byte destination. + /// Converts four associated RGBA byte-magnitude vectors to unassociated scaled vectors. /// - /// The unassociated scaled vector. - /// The associated scaled vector. + /// The associated byte-magnitude vectors. + /// The unassociated scaled vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector4 Associate(Vector4 source) + private static Vector512 ToUnassociatedVector4(Vector512 source) { - float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue); + Vector512 zero = Vector512.Zero; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); - // Associate in byte magnitude before normalization. Multiplying by a normalized alpha and later restoring byte magnitude can move an exact half-byte across its rounding boundary. - source *= storedAlpha; - source.W = storedAlpha; - return source / byte.MaxValue; + // 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 unassociated scaled vectors to the associated representation of an unsigned-byte destination. + /// 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. - internal static void Associate(Span source) + private static void AssociateToByte(Span source) { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(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 sourceBase, i) = Associate(Unsafe.Add(ref sourceBase, 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. /// @@ -85,7 +359,7 @@ internal static partial class Vector4Converters { float alpha = source.W; - if (alpha == 0) + if (alpha <= 0) { return Vector4.Zero; } @@ -97,14 +371,16 @@ internal static partial class Vector4Converters { // Quantization leaves alpha unchanged, so RGB is already associated correctly. Avoiding division preserves exact byte midpoints produced by scaling stored components. source *= byte.MaxValue; - source.W = storedAlpha; - return source; + } + 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; } - // 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; } @@ -114,10 +390,11 @@ internal static partial class Vector4Converters /// The vectors to convert in place. private static void ReassociateToByte(Span source) { - if (Avx512F.IsSupported) + 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 / 4u); + ref Vector512 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / (uint)pixelsPerVector); while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) { @@ -125,12 +402,30 @@ internal static partial class Vector4Converters vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); } - source = source[(source.Length & ~3)..]; + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; } - else if (Avx.IsSupported) + + 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 / 2u); + 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)) { @@ -138,7 +433,7 @@ internal static partial class Vector4Converters vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); } - source = source[(source.Length & ~1)..]; + return; } ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); @@ -159,7 +454,7 @@ internal static partial class Vector4Converters { Vector512 zero = Vector512.Zero; Vector512 byteMax = Vector512.Create((float)byte.MaxValue); - Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + 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; @@ -168,6 +463,7 @@ internal static partial class Vector4Converters 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); } @@ -181,15 +477,40 @@ internal static partial class Vector4Converters { Vector256 zero = Vector256.Zero; Vector256 byteMax = Vector256.Create((float)byte.MaxValue); - Vector256 alpha = Avx.Permute(source, 0b_11_11_11_11); + Vector256 alpha = Vector256.Max(Vector256_.ShuffleNative(source, 0b_11_11_11_11), zero); Vector256 byteAlpha = alpha * byteMax; - Vector256 storedAlpha = Avx.Floor(Avx.Min(Avx.Max(byteAlpha + Vector256.Create(.5F), zero), 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 = Avx.BlendVariable(result, source * byteMax, Avx.CompareEqual(byteAlpha, storedAlpha)); - result = Avx.Blend(result, storedAlpha, 0b_1000_1000); - return Avx.BlendVariable(result, zero, Avx.CompareEqual(alpha, zero)); + 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); } /// @@ -241,22 +562,17 @@ internal static partial class Vector4Converters } /// - /// Converts premultiplied RGBA pixels to vectors. + /// Converts associated RGBA pixels to associated scaled vectors. /// /// The source pixels. /// The destination vectors. - /// The conversion modifier flags. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) + 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)); - ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); } /// @@ -268,39 +584,27 @@ internal static partial class Vector4Converters { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref Rgba32P sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); - - for (nuint i = 0; i < (uint)source.Length; i++) - { - Rgba32P pixel = Unsafe.Add(ref sourceBase, i); - Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); - } + ToUnassociatedVector4(MemoryMarshal.Cast(source), destination[..source.Length]); } /// - /// Converts vectors to premultiplied RGBA pixels. + /// Converts unassociated scaled vectors to premultiplied RGBA pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void FromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) + internal static void FromUnassociatedVector4(Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); destination = destination[..source.Length]; - ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); - SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + AssociateToByte(source); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); } /// /// Converts associated scaled vectors to premultiplied RGBA pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. internal static void FromAssociatedVector4(Span source, Span destination) { @@ -312,16 +616,12 @@ internal static partial class Vector4Converters } /// - /// Converts premultiplied BGRA pixels to vectors. + /// Converts associated BGRA pixels to associated scaled vectors. /// /// The source pixels. /// The destination vectors. - /// The conversion modifier flags. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); @@ -340,7 +640,6 @@ internal static partial class Vector4Converters PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); destination[lastIndex] = source[lastIndex].ToVector4(); - ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); } /// @@ -352,43 +651,44 @@ internal static partial class Vector4Converters { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref Bgra32P sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + destination = destination[..source.Length]; - for (nuint i = 0; i < (uint)source.Length; i++) + if (source.IsEmpty) { - Bgra32P pixel = Unsafe.Add(ref sourceBase, i); - Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + 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 vectors to premultiplied BGRA pixels. + /// Converts unassociated scaled vectors to premultiplied BGRA pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void FromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) + internal static void FromUnassociatedVector4(Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); destination = destination[..source.Length]; - ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + AssociateToByte(source); - // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. Span destinationBytes = MemoryMarshal.Cast(destination); - SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); } /// /// Converts associated scaled vectors to premultiplied BGRA pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. internal static void FromAssociatedVector4(Span source, Span destination) { @@ -403,16 +703,12 @@ internal static partial class Vector4Converters } /// - /// Converts premultiplied ARGB pixels to vectors. + /// Converts associated ARGB pixels to associated scaled vectors. /// /// The source pixels. /// The destination vectors. - /// The conversion modifier flags. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); @@ -431,7 +727,6 @@ internal static partial class Vector4Converters PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); destination[lastIndex] = source[lastIndex].ToVector4(); - ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); } /// @@ -443,43 +738,44 @@ internal static partial class Vector4Converters { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref Argb32P sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + destination = destination[..source.Length]; - for (nuint i = 0; i < (uint)source.Length; i++) + if (source.IsEmpty) { - Argb32P pixel = Unsafe.Add(ref sourceBase, i); - Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + 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 vectors to premultiplied ARGB pixels. + /// Converts unassociated scaled vectors to premultiplied ARGB pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void FromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) + internal static void FromUnassociatedVector4(Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); destination = destination[..source.Length]; - ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + AssociateToByte(source); - // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. Span destinationBytes = MemoryMarshal.Cast(destination); - SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); } /// /// Converts associated scaled vectors to premultiplied ARGB pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. internal static void FromAssociatedVector4(Span source, Span destination) { @@ -494,16 +790,12 @@ internal static partial class Vector4Converters } /// - /// Converts premultiplied ABGR pixels to vectors. + /// Converts associated ABGR pixels to associated scaled vectors. /// /// The source pixels. /// The destination vectors. - /// The conversion modifier flags. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ToVector4( - ReadOnlySpan source, - Span destination, - PixelConversionModifiers modifiers) + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); @@ -522,7 +814,6 @@ internal static partial class Vector4Converters PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); destination[lastIndex] = source[lastIndex].ToVector4(); - ApplyForwardConversionModifiers(destination, modifiers.Remove(PixelConversionModifiers.Premultiply)); } /// @@ -534,43 +825,44 @@ internal static partial class Vector4Converters { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - ref Abgr32P sourceBase = ref MemoryMarshal.GetReference(source); - ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + destination = destination[..source.Length]; - for (nuint i = 0; i < (uint)source.Length; i++) + if (source.IsEmpty) { - Abgr32P pixel = Unsafe.Add(ref sourceBase, i); - Unsafe.Add(ref destinationBase, i) = ToUnassociatedVector4(pixel.R, pixel.G, pixel.B, pixel.A); + 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 vectors to premultiplied ABGR pixels. + /// Converts unassociated scaled vectors to premultiplied ABGR pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. - /// The conversion modifier flags. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void FromVector4( - Span source, - Span destination, - PixelConversionModifiers modifiers) + internal static void FromUnassociatedVector4(Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); destination = destination[..source.Length]; - ApplyBackwardConversionModifiers(source, modifiers.Remove(PixelConversionModifiers.Premultiply)); + AssociateToByte(source); - // NormalizedFloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. Span destinationBytes = MemoryMarshal.Cast(destination); - SimdUtils.NormalizedFloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); } /// /// Converts associated scaled vectors to premultiplied ABGR pixels. /// - /// The source vectors. + /// The source vectors, modified in place during conversion. /// The destination pixels. internal static void FromAssociatedVector4(Span source, Span destination) { diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index d7cac1530..05799961d 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -50,15 +50,43 @@ internal static partial class Vector4Converters 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 @@ internal static partial class Vector4Converters 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 @@ internal static partial class Vector4Converters 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 @@ internal static partial class Vector4Converters 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 @@ internal static partial class Vector4Converters 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 @@ internal static partial class Vector4Converters 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 33b91d151..c08b79f1e 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -48,6 +48,7 @@ internal static partial class Vector4Converters Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); int count = source.Length; + 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 0a0b5660d..d701b0ab5 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 partial class Vector4Converters } /// - /// 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 663d54afc..7023a3cd9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -238,9 +238,10 @@ internal class BokehBlurProcessor : ImageProcessor 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 @@ internal class BokehBlurProcessor : ImageProcessor 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 @@ internal class BokehBlurProcessor : ImageProcessor { 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 @@ internal class BokehBlurProcessor : ImageProcessor 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 @@ internal class BokehBlurProcessor : ImageProcessor 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 bccf19174..409480871 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -78,6 +78,8 @@ internal readonly struct Convolution2DRowOperation : IRowOperation : IRowOperation.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 @@ internal readonly struct Convolution2DRowOperation : IRowOperation.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 @@ internal readonly struct Convolution2DRowOperation : IRowOperation 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 @@ internal readonly struct Convolution2DRowOperation : IRowOperation : IRowOperation 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 @@ internal readonly struct Convolution2DRowOperation : IRowOperation 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 1bbbdb350..1e94812fd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -199,12 +199,14 @@ internal class Convolution2PassProcessor : ImageProcessor 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 @@ internal class Convolution2PassProcessor : ImageProcessor } // 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 @@ internal class Convolution2PassProcessor : ImageProcessor } 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 @@ internal class Convolution2PassProcessor : ImageProcessor 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 @@ internal class Convolution2PassProcessor : ImageProcessor 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 @@ internal class Convolution2PassProcessor : ImageProcessor 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 @@ internal class Convolution2PassProcessor : ImageProcessor // 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 @@ internal class Convolution2PassProcessor : ImageProcessor // 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 @@ internal class Convolution2PassProcessor : ImageProcessor } 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 @@ internal class Convolution2PassProcessor : ImageProcessor 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 @@ internal class Convolution2PassProcessor : ImageProcessor // 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 @@ internal class Convolution2PassProcessor : ImageProcessor 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 feaaf30ce..e599324f9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -161,6 +161,8 @@ internal class ConvolutionProcessor : ImageProcessor 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 @@ internal class ConvolutionProcessor : ImageProcessor // 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 @@ internal class ConvolutionProcessor : ImageProcessor // 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 @@ internal class ConvolutionProcessor : ImageProcessor // 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 @@ internal class ConvolutionProcessor : ImageProcessor } } - 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 eae748166..5b62b9b8a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -122,10 +122,12 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor 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 ca2cabab5..97ddd3be3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -78,7 +78,7 @@ internal readonly struct MedianRowOperation : IRowOperation 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 @@ internal readonly struct MedianRowOperation : IRowOperation } 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 115db8144..36cc5564a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -168,8 +168,8 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I 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 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I } 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 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I } 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 f811bae0f..ddb6d1545 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -119,13 +119,15 @@ internal class OilPaintingProcessor : ImageProcessor 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 @@ internal class OilPaintingProcessor : ImageProcessor 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 @@ internal class OilPaintingProcessor : ImageProcessor 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 37286086c..8b287b727 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -74,11 +74,14 @@ internal class FilterProcessor : ImageProcessor 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 41560d120..bffe6cf63 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -56,7 +56,10 @@ internal sealed class OpaqueProcessor : ImageProcessor 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 @@ internal sealed class OpaqueProcessor : ImageProcessor 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 3d54836c5..b18a52bec 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -144,8 +144,11 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// /// 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// /// 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz 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 8ca4a86a2..624820d2f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -194,7 +194,7 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His 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 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His 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 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His 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 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : 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 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : 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)--; } } @@ -381,9 +381,11 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His 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 e830cc55f..9acca6d40 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs @@ -142,18 +142,19 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor 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 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor 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 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor.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 3ab8f7431..3c07d05c6 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -124,18 +124,19 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat 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 3584f2954..c7e40b420 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs @@ -51,13 +51,13 @@ internal readonly struct GrayscaleLevelsRowOperation : IRowOperation 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 6ac36f3ce..fd01bd8dd 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -142,7 +142,8 @@ internal abstract class HistogramEqualizationProcessor : ImageProcessor< 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 71c1b8457..d669f885a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs @@ -189,8 +189,8 @@ public struct HexadecatreeQuantizer : IQuantizer // 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 struct HexadecatreeQuantizer : IQuantizer { 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 e121aff90..1e3453294 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -39,7 +39,7 @@ public static class QuantizerUtilities /// Returns 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 @@ public static class QuantizerUtilities for (int y = 0; y < source.Height; y++) { Span 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 @@ public static class QuantizerUtilities for (int y = 0; y < source.Height; y++) { Span 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 @@ public static class QuantizerUtilities 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 @@ public static class QuantizerUtilities 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 @@ public static class QuantizerUtilities 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 @@ public static class QuantizerUtilities 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 03d6ac0da..b4b95adc1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -144,7 +144,8 @@ internal struct WuQuantizer : IQuantizer 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 8a76ca42f..b9973eea3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -96,13 +96,16 @@ internal class EntropyCropProcessor : ImageProcessor 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 260a366b9..61cde6e2a 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 @@ internal class AffineTransformProcessor : TransformProcessor, IR 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 @@ internal class AffineTransformProcessor : TransformProcessor, IR 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 @@ internal class AffineTransformProcessor : TransformProcessor, IR { 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 @@ internal class AffineTransformProcessor : TransformProcessor, IR 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 ecf587684..90ca8743c 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 @@ internal class ProjectiveTransformProcessor : TransformProcessor 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 @@ internal class ProjectiveTransformProcessor : TransformProcessor 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 @@ internal class ProjectiveTransformProcessor : TransformProcessor { 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 @@ internal class ProjectiveTransformProcessor : TransformProcessor 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/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 48f4a746f..870c38241 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 @@ internal class ResizeProcessor : TransformProcessor, IResampling 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 @@ internal class ResizeProcessor : TransformProcessor, IResampling 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 a0a8c2173..599882fdb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -169,7 +169,6 @@ internal sealed class ResizeWorker : IDisposable } Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } } @@ -215,12 +214,7 @@ internal sealed class ResizeWorker : IDisposable 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/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs index 20c38d61f..38a09483b 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs @@ -46,7 +46,7 @@ public class ToVector4_Bgra32P : ToVector4 [Benchmark(Baseline = true)] public void PixelOperations_Base() { - new AssociatedAlphaPixelOperations().ToVector4( + new PixelOperations().ToVector4( this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan()); 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 000000000..33bbc1b94 --- /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 000000000..a1916831f --- /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/RgbaDouble.cs b/tests/ImageSharp.Tests/Color/RgbaDouble.cs index 76fdf365c..2487d5fde 100644 --- a/tests/ImageSharp.Tests/Color/RgbaDouble.cs +++ b/tests/ImageSharp.Tests/Color/RgbaDouble.cs @@ -106,6 +106,47 @@ public struct RgbaDouble : IPixel /// 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/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs index 7b7917dbc..cb527cf3b 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -72,6 +72,17 @@ public class ExrMetadataTests 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/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs new file mode 100644 index 000000000..6a0afcd85 --- /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 f98363b7e..de893aca6 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 class TiffDecoderTests : TiffDecoderBaseTester 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 6483b63c5..104ab76a7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs @@ -20,6 +20,26 @@ public class TiffUtilitiesTest 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 class TiffUtilitiesTest [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 4e24b111b..903e997aa 100644 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs @@ -18,7 +18,7 @@ public class ColorNumericsTests 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 1106144c4..35109d352 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 class NumericsTests } [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 class NumericsTests 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 class NumericsTests 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 index f34ca4e05..f63f85344 100644 --- a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -5,6 +5,7 @@ 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; @@ -119,6 +120,153 @@ public class NormalizedByte4PTests : AssociatedAlphaPixelTests Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue); } + + [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); + } + } } /// @@ -156,6 +304,183 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests 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); + 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); + + Assert.Equal(expected, actual); + } + + 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); + + // HalfVector4's native domain is [-1, 1], so the native entry points need the affine encoding of the common scaled values. + unassociatedNative[i] = (unassociatedScaled[i] * 2F) - Vector4.One; + associatedNative[i] = (associatedScaled[i] * 2F) - Vector4.One; + 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()); + } } /// @@ -196,20 +521,77 @@ public class AssociatedAlphaPackedPixelConversionTests [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 { - Rgba32P[] expected = new Rgba32P[byte.MaxValue * 4 + 4]; + 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; - for (int component = 0; component <= byte.MaxValue; component++) + // 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++) { - int index = component * 4; - expected[index] = new Rgba32P((byte)component, 0, 0, byte.MaxValue); - expected[index + 1] = new Rgba32P(0, (byte)component, 0, byte.MaxValue); - expected[index + 2] = new Rgba32P(0, 0, (byte)component, byte.MaxValue); - expected[index + 3] = new Rgba32P(0, 0, 0, (byte)component); + 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); @@ -226,47 +608,152 @@ public class AssociatedAlphaPackedPixelConversionTests private static void AssertScalarAndBulkAssociatedVectorsAreEqual(Func createPixel) where TPixel : unmanaged, IPixel { - TPixel[] pixels = new TPixel[byte.MaxValue * 4 + 4]; - Vector4[] actual = new Vector4[pixels.Length]; + // 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; - pixels[index] = createPixel((byte)component, 0, 0, byte.MaxValue); - pixels[index + 1] = createPixel(0, (byte)component, 0, byte.MaxValue); - pixels[index + 2] = createPixel(0, 0, (byte)component, byte.MaxValue); - pixels[index + 3] = createPixel(0, 0, 0, (byte)component); + 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); } - PixelOperations.Instance.ToAssociatedScaledVector4(Configuration.Default, pixels, actual); + source[^1] = createPixel(37, 73, 109, 151); - for (int i = 0; i < pixels.Length; i++) + foreach (int length in lengths) { - Assert.Equal(pixels[i].ToScaledVector4(), actual[i]); + 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 { - const int count = 259; - Vector4[] vectors = new Vector4[count]; - TPixel[] expected = new TPixel[count]; - TPixel[] actual = new TPixel[count]; + // 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 < vectors.Length; i++) + for (int i = 0; i < source.Length; i++) { - // Alternating exact byte alpha values with fractional values covers both reassociation branches. The odd length also exercises the SIMD remainder. + // Alternating exact byte alpha values with fractional values covers both reassociation branches. float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F; - vectors[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; - vectors[i].W = alpha; - expected[i] = operations.FromAssociatedScaledVector4(vectors[i]); + source[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; + source[i].W = alpha; } - operations.FromAssociatedScaledVector4(Configuration.Default, vectors, actual); + 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]); + } - Assert.Equal(expected, actual); + 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); + } } } @@ -380,11 +867,12 @@ public class AssociatedToUnassociatedPackedPixelConversionTests } [Fact] - public void Rgba32PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() + public void Rgba32PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() { for (int alpha = 0; alpha <= byte.MaxValue; alpha++) { - // Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared. + // 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. @@ -402,7 +890,7 @@ public class AssociatedToUnassociatedPackedPixelConversionTests } [Fact] - public void ColorFromRgba32PPreservesEveryValidAssociatedComponent() + public void ColorFromRgba32PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() { const int pairCount = 32896; Rgba32P[] sourcePixels = new Rgba32P[pairCount]; @@ -432,23 +920,23 @@ public class AssociatedToUnassociatedPackedPixelConversionTests } [Fact] - public void Rgba32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + public void Rgba32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); [Fact] - public void Bgra32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + public void Bgra32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); [Fact] - public void Argb32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + public void Argb32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); [Fact] - public void Abgr32PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + public void Abgr32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); [Fact] - public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryValidAssociatedComponent() + public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() { for (int alpha = 0; alpha < byte.MaxValue; alpha++) { @@ -468,7 +956,7 @@ public class AssociatedToUnassociatedPackedPixelConversionTests } [Fact] - public void ColorFromNormalizedByte4PPreservesEveryValidAssociatedComponent() + public void ColorFromNormalizedByte4PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() { const int pairCount = 32640; NormalizedByte4P[] sourcePixels = new NormalizedByte4P[pairCount]; @@ -499,7 +987,7 @@ public class AssociatedToUnassociatedPackedPixelConversionTests } [Fact] - public void NormalizedByte4PToBgra32RoundTripPreservesEveryValidAssociatedComponent() + public void NormalizedByte4PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() { const int pairCount = 32640; const int channelCount = 3; @@ -554,7 +1042,8 @@ public class AssociatedToUnassociatedPackedPixelConversionTests for (int alpha = 0; alpha <= byte.MaxValue; alpha++) { - // Associated components greater than alpha are invalid, so the exhaustive domain is triangular rather than 256 squared. + // 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. diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs new file mode 100644 index 000000000..036f1cd71 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs @@ -0,0 +1,493 @@ +// 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; + +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 alpha conversion for formats whose native vector space differs from scaled color space. +/// +[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 * 65534F) - new Vector4(32767F)); + + [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 * 2F) - Vector4.One); + + [Fact] + public void HalfVector4PNativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void HalfSingleFromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, 0F, 0F, vector.W)); + + [Fact] + public void HalfVector2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 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 * 65534F) - 32767F, (vector.Y * 65534F) - 32767F, 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 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/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 935b4acc2..dbff54140 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -97,8 +97,8 @@ public abstract partial class PixelConverterTests { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - Vector4 vector = PixelOperations.Instance.ToUnassociatedScaledVector4(sp); - dp = PixelOperations.Instance.FromUnassociatedScaledVector4(vector); + Vector4 vector = sp.ToUnassociatedScaledVector4(); + dp = TDestinationPixel.FromUnassociatedScaledVector4(vector); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 196c98449..7be839e72 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -161,9 +161,30 @@ public abstract class PixelOperationsTests : MeasureFixture [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); + } + } + + void ExpectedAction(ref Vector4 v) + { + if (this.HasAssociatedAlpha) + { + Numerics.UnPremultiply(ref v); + } + + v = SRgbCompanding.Compress(v); + + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } Vector4[] source = CreateVector4TestData(count, SourceAction); TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); @@ -183,25 +204,17 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void FromPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) - { - if (!this.HasAssociatedAlpha) - { - 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.HasAssociatedAlpha) - { - 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, @@ -258,21 +271,18 @@ public abstract class PixelOperationsTests : MeasureFixture void SourceAction(ref Vector4 v) { v = SRgbCompanding.Expand(v); - - if (!this.HasAssociatedAlpha) - { - Numerics.Premultiply(ref v); - } + Numerics.Premultiply(ref v); } void ExpectedAction(ref Vector4 v) { - if (!this.HasAssociatedAlpha) + Numerics.UnPremultiply(ref v); + v = SRgbCompanding.Compress(v); + + if (this.HasAssociatedAlpha) { - Numerics.UnPremultiply(ref v); + Numerics.Premultiply(ref v); } - - v = SRgbCompanding.Compress(v); } Vector4[] source = CreateVector4TestData(count, SourceAction); @@ -381,9 +391,26 @@ public abstract class PixelOperationsTests : MeasureFixture { 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); @@ -402,21 +429,15 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void ToPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) - { - } + TPixel[] source = CreatePixelTestData(count); + Vector4[] expected = new Vector4[count]; - void ExpectedAction(ref Vector4 v) + for (int i = 0; i < source.Length; i++) { - if (!this.HasAssociatedAlpha) - { - Numerics.Premultiply(ref v); - } + // 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(); } - TPixel[] source = CreatePixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction); - TestOperation( source, expected, @@ -458,16 +479,21 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } void ExpectedAction(ref Vector4 v) { - v = SRgbCompanding.Expand(v); - - if (!this.HasAssociatedAlpha) + if (this.HasAssociatedAlpha) { - Numerics.Premultiply(ref v); + Numerics.UnPremultiply(ref v); } + + v = SRgbCompanding.Expand(v); + Numerics.Premultiply(ref v); } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); @@ -1086,9 +1112,8 @@ public abstract class PixelOperationsTests : MeasureFixture return expected; } - private static Vector4 ToUnassociatedScaledVector4(TPixel source) - // The scalar conversion boundary owns representation-specific rounding. Byte-export tests compare each bulk operation with that scalar result. - => PixelOperations.Instance.ToUnassociatedScaledVector4(source); + // 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, diff --git a/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs b/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs new file mode 100644 index 000000000..485f64174 --- /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/Processors/Quantization/HexadecatreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs index 4ef215930..b154551e2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs @@ -59,4 +59,18 @@ public class HexadecatreeQuantizerTests 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 023d47886..7389c4b89 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 class ResizeTests 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 000000000..ddad67b94 --- /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 28bfcfaa8..a3de7b3fe 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -271,6 +271,18 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat 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 class TestFormat : IImageFormatConfigurationModule, IImageFormat 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 813ed505d..ee1d27471 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -78,6 +78,6 @@ public abstract partial class TestImageProvider : IXunitSerializable 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 405d048fc..12a7ad4e2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -158,15 +158,17 @@ public abstract partial class TestImageProvider : IXunitSerializable 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 @@ public abstract partial class TestImageProvider : IXunitSerializable } 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 73777ef60..c7883fac1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -278,7 +278,7 @@ public class ImagingTestCaseUtility 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 000000000..aed4ef243 --- /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 be2356d65..b51e4e2d4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -826,14 +826,17 @@ public static class TestImageExtensions 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 f0344e2b9..174766daf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -35,7 +35,7 @@ public class TestPixel : IXunitSerializable 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 46fb7159e..714dbd223 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -31,7 +31,7 @@ public class TestUtilityExtensionsTests 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 f705b63df..96482b46e 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/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png index 5b72c3732..a3317a6b4 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 From 2ffa9fb07bd15364cd2c8e52f34666da225d5d85 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 11:01:44 +1000 Subject: [PATCH 207/240] Add binary16 pixel formats and scaling fixes Introduces `RgbaHalf` and `RgbaHalfP` plus bulk pixel-operation support, updates AOT seeding, and routes EXR decoding through the new scaled conversions. Also fixes scaled/native range handling for half, SNORM, and signed integer pixel formats, and expands tests for the new layouts and edge cases. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 4 + src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 12 +- src/ImageSharp/PixelFormats/HalfTypeHelper.cs | 57 ++ .../PixelFormats/PixelImplementations/A8.cs | 7 +- .../PixelImplementations/Bgr565.cs | 8 +- .../PixelImplementations/Bgra4444.cs | 9 +- .../PixelImplementations/Bgra5551.cs | 11 +- .../PixelImplementations/Byte4.cs | 9 +- .../PixelImplementations/HalfSingle.cs | 26 +- .../PixelImplementations/HalfVector2.cs | 26 +- .../PixelImplementations/HalfVector4.cs | 36 +- .../PixelImplementations/HalfVector4P.cs | 73 +- .../PixelImplementations/NormalizedByte2.cs | 36 +- .../PixelImplementations/NormalizedByte4.cs | 39 +- .../PixelImplementations/NormalizedByte4P.cs | 44 +- .../PixelImplementations/NormalizedShort2.cs | 35 +- .../PixelImplementations/NormalizedShort4.cs | 38 +- .../HalfSingle.PixelOperations.cs | 12 - .../HalfVector2.PixelOperations.cs | 12 - .../HalfVector4.PixelOperations.cs | 48 +- .../HalfVector4P.PixelOperations.cs | 701 ++++----------- .../NormalizedByte4P.PixelOperations.cs | 66 +- .../RgbaHalf.PixelOperations.cs | 72 ++ .../RgbaHalfP.PixelOperations.cs | 795 ++++++++++++++++++ .../PixelOperations/Short2.PixelOperations.cs | 6 +- .../PixelOperations/Short4.PixelOperations.cs | 4 +- .../PixelFormats/PixelImplementations/Rg32.cs | 9 +- .../PixelImplementations/Rgba1010102.cs | 9 +- .../PixelImplementations/Rgba64.cs | 9 +- .../PixelImplementations/RgbaHalf.cs | 227 +++++ .../PixelImplementations/RgbaHalfP.cs | 242 ++++++ .../PixelImplementations/Short2.cs | 25 +- .../PixelImplementations/Short4.cs | 30 +- .../Utils/SignedShort4PixelOperations.cs | 57 +- ...tor4Converters.AssociatedRgbaCompatible.cs | 19 +- .../PixelFormats/AssociatedAlphaPixelTests.cs | 96 ++- .../PixelFormats/HalfSingleTests.cs | 54 +- .../PixelFormats/HalfVector2Tests.cs | 29 +- .../PixelFormats/HalfVector4Tests.cs | 36 +- .../PixelFormats/NormalizedByte2Tests.cs | 31 + .../PixelFormats/NormalizedByte4Tests.cs | 28 + .../PixelFormats/NormalizedShort2Tests.cs | 31 + .../PixelFormats/NormalizedShort4Tests.cs | 28 + .../PixelAlphaRepresentationTests.cs | 16 +- ...elOperationsTests.Specialized.Generated.cs | 30 + .../Generated/_Common.ttinclude | 6 +- .../PixelOperations/PixelOperationsTests.cs | 61 +- .../PixelFormats/RgbaHalfTests.cs | 283 +++++++ .../PixelFormats/Short2Tests.cs | 48 +- .../PixelFormats/Short4Tests.cs | 47 +- .../Processing/Filters/BrightnessTest.cs | 7 +- 51 files changed, 2656 insertions(+), 988 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index bccbcc11a..6d37fd5ce 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -114,6 +114,8 @@ internal static class AotCompilerTools Seed(); Seed(); Seed(); + Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -199,6 +201,8 @@ internal static class AotCompilerTools img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index a0bbdd30e..2cb382de1 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,11 +209,10 @@ internal sealed class ExrDecoderCore : ImageDecoderCore for (int x = 0; x < width; x++) { - HalfVector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1.0f); + Vector4 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : 1F); - // OpenEXR channel values are associated. The destination conversion retains that representation for associated - // pixels and unassociates only when the destination stores straight alpha. - pixelRow[x] = TPixel.FromAssociatedVector4(pixelValue.ToVector4()); + // OpenEXR channels are associated color values, not values in the destination pixel format's native numeric range. + pixelRow[x] = TPixel.FromAssociatedScaledVector4(pixelValue); } decodedRows++; @@ -292,8 +292,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore { Rgba128 pixelValue = new(redPixelData[x], greenPixelData[x], bluePixelData[x], hasAlpha ? alphaPixelData[x] : uint.MaxValue); - // Preserve the file's associated-alpha convention while converting from the native unsigned channel domain. - pixelRow[x] = TPixel.FromAssociatedVector4(pixelValue.ToVector4()); + // Rgba128 normalizes the unsigned channel values before the destination applies its own numeric representation. + pixelRow[x] = TPixel.FromAssociatedScaledVector4(pixelValue.ToVector4()); } decodedRows++; diff --git a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs index 473e2ed28..53f22eb09 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.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.Intrinsics; @@ -11,6 +12,14 @@ 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; @@ -40,6 +49,54 @@ internal static class HalfTypeHelper [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. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index 65f70867c..8a17831a1 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 { /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index a77174a71..338c55fe3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -7,14 +7,12 @@ using System.Runtime.CompilerServices; 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. diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 97c2eabd0..17711835a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -7,11 +7,12 @@ using System.Runtime.CompilerServices; 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 { /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 799c2ce5c..22a2c1f99 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -7,12 +7,13 @@ using System.Runtime.CompilerServices; 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 { /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index d8e9ca9ec..c0372ab97 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -8,11 +8,12 @@ using System.Runtime.Intrinsics; 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(); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index c1204efed..cbb0cf086 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; 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); } /// @@ -109,20 +110,15 @@ public partial struct HalfSingle : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfSingle FromAssociatedVector4(Vector4 source) { - // Only the stored color component uses the native [-1, 1] encoding; W remains the normalized source alpha. - source.X = (source.X + 1F) / 2F; + // 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) - { - float scaled = source.X; - scaled *= 2F; - scaled--; - return new HalfSingle { PackedValue = HalfTypeHelper.Pack(scaled) }; - } + => new() { PackedValue = HalfTypeHelper.Pack(HalfTypeHelper.FromScaled(source.X)) }; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 900ca07b4..ad6c7aefa 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; 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); } @@ -121,9 +121,10 @@ public partial struct HalfVector2 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector2 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; + // 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); } @@ -131,9 +132,8 @@ public partial struct HalfVector2 : IPixel, IPackedVector [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 dec77f048..42531937c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; 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 partial struct HalfVector4 : IPixel, IPackedVector /// [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)] @@ -112,10 +108,8 @@ public partial struct HalfVector4 : IPixel, IPackedVector { 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; + // Association is defined in scaled color space, so map the associated result back to the native binary16 range. + return HalfTypeHelper.FromScaled(vector); } /// @@ -138,20 +132,14 @@ public partial struct HalfVector4 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4 FromAssociatedVector4(Vector4 source) { - // Map the affine native encoding to logical [0, 1] space before unassociating. - source += Vector4.One; - source /= 2F; + // Restore scaled opacity before unassociating the color channels. + source = HalfTypeHelper.ToScaled(source); return FromAssociatedScaledVector4(source); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromScaledVector4(Vector4 source) - { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); - } + public static HalfVector4 FromScaledVector4(Vector4 source) => FromVector4(HalfTypeHelper.FromScaled(source)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs index 4a51c21af..50fc27255 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs @@ -10,7 +10,9 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Packed pixel type containing four associated 16-bit floating-point values. /// /// -/// The native packed and vector representations use associated alpha. +/// 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 { @@ -58,13 +60,8 @@ public partial struct HalfVector4P : IPixel, IPackedVector } /// - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2F; - return scaled; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => HalfTypeHelper.ToScaled(this.ToVector4()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -90,11 +87,8 @@ public partial struct HalfVector4P : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToUnassociatedVector4() { - // Association is defined in the common scaled domain. Binary16-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; + return HalfTypeHelper.FromScaled(vector); } /// @@ -121,9 +115,7 @@ public partial struct HalfVector4P : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4P FromUnassociatedVector4(Vector4 source) { - // Convert the binary16-native range to the common scaled domain before associating because native W is not opacity. - source += Vector4.One; - source /= 2F; + source = HalfTypeHelper.ToScaled(source); return FromUnassociatedScaledVector4(source); } @@ -131,9 +123,7 @@ public partial struct HalfVector4P : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4P FromAssociatedVector4(Vector4 source) { - // Reassociation must also operate in scaled space so RGB follows the quantized scaled alpha rather than the binary16-native W component. - source += Vector4.One; - source /= 2F; + source = HalfTypeHelper.ToScaled(source); return FromAssociatedScaledVector4(source); } @@ -145,20 +135,6 @@ public partial struct HalfVector4P : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4P FromAssociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Reassociate(source)); - /// - /// Packs an associated scaled vector into binary16-native storage. - /// - /// The associated scaled vector. - /// The packed pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static HalfVector4P PackAssociatedScaledVector4(Vector4 source) - { - // Binary16 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 HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); @@ -224,9 +200,8 @@ public partial struct HalfVector4P : IPixel, IPackedVector { source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); - // Quantize alpha through the destination's native half representation before RGB is associated with it. - float nativeAlpha = (source.W * 2F) - 1F; - source.W = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; + // RGB must use the scaled alpha that the binary16 representation can reproduce. + source.W = QuantizeScaledAlpha(source.W); Numerics.Premultiply(ref source); return source; } @@ -246,9 +221,7 @@ public partial struct HalfVector4P : IPixel, IPackedVector return Vector4.Zero; } - // The binary16-native alpha range is [-1, 1]. Clamp before packing so out-of-range scaled alpha cannot escape the pixel's declared [0, 1] opacity range. - float nativeAlpha = Numerics.Clamp((alpha * 2F) - 1F, -1F, 1F); - float storedAlpha = (HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha)) + 1F) / 2F; + 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; @@ -257,6 +230,30 @@ public partial struct HalfVector4P : IPixel, IPackedVector 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. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index 317e59574..6039bd9d7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -7,15 +7,20 @@ using System.Runtime.CompilerServices; 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 partial struct NormalizedByte2 : IPixel, IPackedVector> 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); } @@ -201,9 +211,15 @@ public partial struct NormalizedByte2 : IPixel, IPackedVector /// 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 09ddd0e91..2f91718cf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -8,16 +8,20 @@ using System.Runtime.Intrinsics; 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(); /// @@ -73,21 +77,28 @@ public partial struct NormalizedByte4 : IPixel, IPackedVector> 0) & 0xFF) + MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) + MaxPos, - (sbyte)((this.PackedValue >> 16) & 0xFF) + MaxPos, - (sbyte)((this.PackedValue >> 24) & 0xFF) + MaxPos); + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); - return scaled / ScaledMagnitude; + // 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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs index 4f6df4c6f..1172aafb9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -11,13 +11,17 @@ namespace SixLabors.ImageSharp.PixelFormats; /// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1. /// /// -/// The native packed and vector representations use associated alpha. +/// 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(); /// @@ -65,12 +69,13 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector { // 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) + MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) + MaxPos, - (sbyte)((this.PackedValue >> 16) & 0xFF) + MaxPos, - (sbyte)((this.PackedValue >> 24) & 0xFF) + MaxPos); + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); - return scaled / ScaledMagnitude; + // 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; } /// @@ -82,11 +87,17 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); /// - 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; + } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -244,10 +255,13 @@ public partial struct NormalizedByte4P : IPixel, IPackedVector { // Offset signed storage into exact nonnegative byte magnitudes before division so the quotient retains the destination's byte-rounding midpoint. Vector4 vector = new( - (sbyte)(source.PackedValue >> 0) + MaxPos, - (sbyte)(source.PackedValue >> 8) + MaxPos, - (sbyte)(source.PackedValue >> 16) + MaxPos, - (sbyte)(source.PackedValue >> 24) + MaxPos); + (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) { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index dfc42594d..3da0f7f0b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -7,15 +7,19 @@ using System.Runtime.CompilerServices; 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 partial struct NormalizedShort2 : IPixel, IPackedVector [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); } @@ -203,9 +212,15 @@ public partial struct NormalizedShort2 : IPixel, IPackedVector /// /// 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 a5ab585df..7acac0b7d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -8,11 +8,14 @@ using System.Runtime.Intrinsics; 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; @@ -74,21 +77,28 @@ public partial struct NormalizedShort4 : IPixel, IPackedVector { // 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) + MaxPos, - (short)((this.PackedValue >> 0x10) & 0xFFFF) + MaxPos, - (short)((this.PackedValue >> 0x20) & 0xFFFF) + MaxPos, - (short)((this.PackedValue >> 0x30) & 0xFFFF) + MaxPos); + (short)((this.PackedValue >> 0x00) & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); - return scaled / ScaledMagnitude; + // 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() diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs index ec1fe3dd3..07428f484 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.PixelFormats; @@ -16,9 +15,6 @@ public partial struct HalfSingle /// internal class PixelOperations : PixelOperations { - private static readonly Vector4 NativeOffset = new(1F, 0F, 0F, 0F); - private static readonly Vector4 NativeDivisor = new(2F, 1F, 1F, 1F); - // Alpha is implicitly one, so both outward representations already contain associated color components. /// @@ -26,13 +22,5 @@ public partial struct HalfSingle /// 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 uses the affine native range; W must remain normalized so the scaled path can unassociate the source color. - Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); - this.FromAssociatedScaledVector4Destructive(configuration, source, destination); - } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs index 3f46d61f7..e167c4eb7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.PixelFormats; @@ -16,9 +15,6 @@ public partial struct HalfVector2 /// 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. /// @@ -26,13 +22,5 @@ public partial struct HalfVector2 /// 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. W is the normalized alpha used by the scaled unassociation step. - Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); - this.FromAssociatedScaledVector4Destructive(configuration, source, destination); - } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs index e29055366..9eae7d5cb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs @@ -17,14 +17,18 @@ public partial struct HalfVector4 /// internal class PixelOperations : PixelOperations { - private static readonly Vector4 NativeScale = new(2F); + 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)); - HalfVector4P.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length], false); + // 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]); } /// @@ -34,11 +38,11 @@ public partial struct HalfVector4 destination = destination[..source.Length]; - // HalfVector4P has the same four-half layout, allowing both formats to share the SIMD half expansion. Association must - // still occur in scaled space because the native half values use an affine [-1, 1] color coordinate system. - HalfVector4P.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination, true); + // 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, NativeScale, -Vector4.One); + Vector4Converters.MultiplyThenAdd(destination, ScaledToNativeMultiplier, ScaledToNativeOffset); } /// @@ -46,17 +50,16 @@ public partial struct HalfVector4 { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - HalfVector4P.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length], true); + 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) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - destination = destination[..source.Length]; - HalfVector4P.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination, true); - Numerics.Premultiply(destination); + this.ToUnassociatedScaledVector4(configuration, source, destination); + Numerics.Premultiply(destination[..source.Length]); } /// @@ -64,8 +67,8 @@ public partial struct HalfVector4 { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; - HalfVector4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), false); + // 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])); } /// @@ -73,12 +76,11 @@ public partial struct HalfVector4 { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; - - // Normalize native alpha with RGB before unassociation, then reuse the shared half packing kernel in scaled mode. - Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + // Restore normalized opacity before unassociating, then return the result to the native binary16 range. + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); Numerics.UnPremultiply(source); - HalfVector4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); } /// @@ -86,8 +88,8 @@ public partial struct HalfVector4 { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; - HalfVector4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); } /// @@ -95,9 +97,9 @@ public partial struct HalfVector4 { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; Numerics.UnPremultiply(source); - HalfVector4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + 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 index 1646dd4d3..52b2dbf37 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs @@ -6,6 +6,7 @@ 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; @@ -19,12 +20,16 @@ public partial struct HalfVector4P /// 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) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - UnpackUnassociated(source, destination[..source.Length], false); + this.ToUnassociatedScaledVector4(configuration, source, destination); + Vector4Converters.MultiplyThenAdd(destination[..source.Length], ScaledToNativeMultiplier, ScaledToNativeOffset); } /// @@ -32,41 +37,44 @@ public partial struct HalfVector4P { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - Unpack(source, destination[..source.Length], false); + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); } /// - protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - destination = destination[..source.Length]; - PackUnassociated(source, destination, false); + this.ToAssociatedScaledVector4(configuration, source, destination); + Numerics.UnPremultiply(destination[..source.Length]); } /// - protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); destination = destination[..source.Length]; - PackAssociated(source, destination, false); + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); } /// - protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - UnpackUnassociated(source, destination[..source.Length], true); + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); } /// - protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - Unpack(source, destination[..source.Length], true); + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); } /// @@ -74,8 +82,8 @@ public partial struct HalfVector4P { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; - PackUnassociated(source, destination, true); + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); } /// @@ -83,625 +91,197 @@ public partial struct HalfVector4P { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - destination = destination[..source.Length]; - PackAssociated(source, destination, true); + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); } /// - /// Unpacks native or scaled binary16 components into vectors. + /// Associates scaled vectors with the alpha values representable by native binary16 storage. /// - /// The packed source pixels. - /// The destination vectors. - /// Whether to produce scaled vectors. - internal static void Unpack(ReadOnlySpan source, Span destination, bool scaled) + /// The vectors to convert in place. + private static void Associate(Span source) { - 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; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); 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); - - if (scaled) - { - lower = ToScaled(lower); - upper = ToScaled(upper); - } - - 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); - - if (scaled) - { - lower = ToScaled(lower); - upper = ToScaled(upper); - } - - 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); + int vectorsPerRegister = Vector512.Count / Vector128.Count; - if (scaled) - { - lower = ToScaled(lower); - upper = ToScaled(upper); - } - - Vector128.StoreUnsafe(lower, ref destinationBase, (nuint)i); - Vector128.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector128.Count)); - } - - if (i < componentCount) + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) { - // A pixel contains four halves, 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 vector = HalfTypeHelper.Unpack(packed).Lower; - - if (scaled) - { - vector = ToScaled(vector); - } - - Vector128.StoreUnsafe(vector, ref destinationBase, (nuint)i); - } - - return; - } - - ref HalfVector4P pixelBase = ref Unsafe.As(ref sourceBase); - ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); - - for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) - { - HalfVector4P pixel = Unsafe.Add(ref pixelBase, (uint)pixelIndex); - Unsafe.Add(ref vectorBase, (uint)pixelIndex) = scaled ? pixel.ToScaledVector4() : pixel.ToVector4(); - } - } - - /// - /// Unpacks associated binary16 storage directly into unassociated native or scaled vectors. - /// - /// The packed source pixels. - /// The destination vectors. - /// Whether to produce scaled vectors. - private static void UnpackUnassociated(ReadOnlySpan source, Span destination, bool scaled) - { - 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); - Vector512.StoreUnsafe(ToUnassociated(upper, scaled), ref destinationBase, (nuint)(i + Vector512.Count)); + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); } } 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); - Vector256.StoreUnsafe(ToUnassociated(upper, scaled), 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(ToUnassociated(lower, scaled), ref destinationBase, (nuint)i); - Vector128.StoreUnsafe(ToUnassociated(upper, scaled), ref destinationBase, (nuint)(i + Vector128.Count)); - } + int vectorsPerRegister = Vector256.Count / Vector128.Count; - if (i < componentCount) + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) { - // A pixel contains four halves, 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 vector = HalfTypeHelper.Unpack(packed).Lower; - Vector128.StoreUnsafe(ToUnassociated(vector, scaled), ref destinationBase, (nuint)i); - } - - return; - } - - ref HalfVector4P pixelBase = ref Unsafe.As(ref sourceBase); - ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); - - for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) - { - HalfVector4P pixel = Unsafe.Add(ref pixelBase, (uint)pixelIndex); - Unsafe.Add(ref vectorBase, (uint)pixelIndex) = scaled ? pixel.ToUnassociatedScaledVector4() : pixel.ToUnassociatedVector4(); - } - } - - /// - /// Packs native or scaled vectors into binary16 storage. - /// - /// The source vectors. - /// The destination pixels. - /// Whether the source contains scaled vectors. - internal static void Pack(Span source, Span destination, bool scaled) - { - 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 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); - Vector512.StoreUnsafe(packed, 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 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); - Vector256.StoreUnsafe(packed, ref destinationBase, (nuint)i); + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); } } if (Vector128.IsHardwareAccelerated) { - for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + for (; i < source.Length; i++) { - Vector128 lower = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); - Vector128 upper = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)); - Vector128 packed = scaled ? PackScaled(lower, upper) : HalfTypeHelper.Pack(lower, upper); - Vector128.StoreUnsafe(packed, ref destinationBase, (nuint)i); - } - - if (i < componentCount) - { - // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. - Vector128 vector = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); - Vector128 packed = scaled ? PackScaled(vector, vector) : HalfTypeHelper.Pack(vector, vector); - Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); } return; } - ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); - ref HalfVector4P pixelBase = ref Unsafe.As(ref destinationBase); - - for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + for (; i < source.Length; i++) { - Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); - Unsafe.Add(ref pixelBase, (uint)pixelIndex) = scaled ? FromScaledVector4(vector) : FromVector4(vector); + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Associate(Unsafe.Add(ref sourceBase, (uint)i)); } } /// - /// Associates unassociated native or scaled vectors and packs them into binary16 storage in one pass. + /// Reassociates scaled vectors with the alpha values representable by native binary16 storage. /// - /// The unassociated vectors. - /// The destination pixels. - /// Whether the source contains scaled vectors. - private static void PackUnassociated(Span source, Span destination, bool scaled) + /// The vectors to convert in place. + private static void Reassociate(Span source) { - 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; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); int i = 0; - // Quantize alpha to the binary16 value the destination will store before associating RGB. The kernels replace W separately - // so alpha is stored unchanged instead of being multiplied by itself. if (Vector512.IsHardwareAccelerated) { - for (; i <= componentCount - Vector512.Count; i += Vector512.Count) - { - Vector512 lower = Associate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector512 upper = Associate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)), scaled); - Vector512.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); - } - } - - if (Vector256.IsHardwareAccelerated) - { - for (; i <= componentCount - Vector256.Count; i += Vector256.Count) - { - Vector256 lower = Associate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector256 upper = Associate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)), scaled); - Vector256.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); - } - } - - if (Vector128.IsHardwareAccelerated) - { - for (; i <= componentCount - Vector128.Count; i += Vector128.Count) - { - Vector128 lower = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector128 upper = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)), scaled); - Vector128.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); - } - - if (i < componentCount) - { - // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. - Vector128 vector = Associate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector128 packed = PackScaled(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 HalfVector4P pixelBase = ref Unsafe.As(ref destinationBase); - - for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) - { - Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); - - // Qualify the containing pixel type because this nested class also declares bulk overloads with these names. - Unsafe.Add(ref pixelBase, (uint)pixelIndex) = scaled ? HalfVector4P.FromUnassociatedScaledVector4(vector) : HalfVector4P.FromUnassociatedVector4(vector); - } - } + int vectorsPerRegister = Vector512.Count / Vector128.Count; - /// - /// Reassociates associated native or scaled vectors and packs them into binary16 storage in one pass. - /// - /// The associated vectors. - /// The destination pixels. - /// Whether the source contains scaled vectors. - private static void PackAssociated(Span source, Span destination, bool scaled) - { - 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; - - // Preserve straight color across binary16 alpha quantization by scaling RGB by storedAlpha / inputAlpha. The kernels replace - // W, clamp RGB to stored alpha, and clear zero-alpha vectors so the packed result remains valid associated color. - if (Vector512.IsHardwareAccelerated) - { - for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) { - Vector512 lower = Reassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector512 upper = Reassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)), scaled); - Vector512.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); } } if (Vector256.IsHardwareAccelerated) { - for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + int vectorsPerRegister = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) { - Vector256 lower = Reassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector256 upper = Reassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)), scaled); - Vector256.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); } } if (Vector128.IsHardwareAccelerated) { - for (; i <= componentCount - Vector128.Count; i += Vector128.Count) - { - Vector128 lower = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector128 upper = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)), scaled); - Vector128.StoreUnsafe(PackScaled(lower, upper), ref destinationBase, (nuint)i); - } - - if (i < componentCount) + for (; i < source.Length; i++) { - // Duplicate the final vector to use the two-input narrowing primitive, then store only its lower pixel. - Vector128 vector = Reassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i), scaled); - Vector128 packed = PackScaled(vector, vector); - Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); } return; } - ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); - ref HalfVector4P pixelBase = ref Unsafe.As(ref destinationBase); - - for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + for (; i < source.Length; i++) { - Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); - - // Qualify the containing pixel type because this nested class also declares bulk overloads with these names. - Unsafe.Add(ref pixelBase, (uint)pixelIndex) = scaled ? HalfVector4P.FromAssociatedScaledVector4(vector) : HalfVector4P.FromAssociatedVector4(vector); + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Reassociate(Unsafe.Add(ref sourceBase, (uint)i)); } } /// - /// Converts native binary16 values to the normalized range used by pixel conversions. - /// - /// The native values. - /// The scaled values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ToScaled(Vector128 source) => (source + Vector128.One) / Vector128.Create(2F); - - /// - /// Converts native binary16 values to the normalized range used by pixel conversions. - /// - /// The native values. - /// The scaled values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ToScaled(Vector256 source) => (source + Vector256.One) / Vector256.Create(2F); - - /// - /// Converts native binary16 values to the normalized range used by pixel conversions. - /// - /// The native values. - /// The scaled values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 ToScaled(Vector512 source) => (source + Vector512.One) / Vector512.Create(2F); - - /// - /// Converts associated native binary16 values to unassociated native or scaled vectors. - /// - /// The associated native values. - /// Whether to produce scaled vectors. - /// The unassociated vectors. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ToUnassociated(Vector128 source, bool scaled) - { - source = ToScaled(source); - Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); - Vector128 result = Vector128.ConditionalSelect(Vector128.Equals(alpha, Vector128.Zero), source, source / alpha); - Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); - result = Vector128.ConditionalSelect(alphaMask, alpha, result); - - // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. - return scaled ? result : (result * Vector128.Create(2F)) - Vector128.One; - } - - /// - /// Converts associated native binary16 values to unassociated native or scaled vectors. - /// - /// The associated native values. - /// Whether to produce scaled vectors. - /// The unassociated vectors. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ToUnassociated(Vector256 source, bool scaled) - { - source = ToScaled(source); - Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); - Vector256 result = Vector256.ConditionalSelect(Vector256.Equals(alpha, Vector256.Zero), source, source / alpha); - Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); - result = Vector256.ConditionalSelect(alphaMask, alpha, result); - - // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. - return scaled ? result : (result * Vector256.Create(2F)) - Vector256.One; - } - - /// - /// Converts associated native binary16 values to unassociated native or scaled vectors. - /// - /// The associated native values. - /// Whether to produce scaled vectors. - /// The unassociated vectors. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 ToUnassociated(Vector512 source, bool scaled) - { - source = ToScaled(source); - Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); - Vector512 result = Vector512.ConditionalSelect(Vector512.Equals(alpha, Vector512.Zero), source, source / 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, alpha, result); - - // Binary16-native W is an affine encoding rather than opacity, so map back only after unassociation in scaled space. - return scaled ? result : (result * Vector512.Create(2F)) - Vector512.One; - } - - /// - /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// Converts an unassociated scaled vector to associated scaled components. /// /// The unassociated vectors. - /// Whether the source contains scaled vectors. - /// The associated scaled vectors. + /// The associated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 Associate(Vector128 source, bool scaled) + private static Vector128 Associate(Vector128 source) { - Vector128 zero = Vector128.Zero; - Vector128 one = Vector128.One; - - if (!scaled) - { - // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. - source = ToScaled(source); - } - - source = Vector128.Min(Vector128.Max(source, zero), one); + source = ClampUnit(source); Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); - Vector128 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector128.Create(2F)) - one) + one) / Vector128.Create(2F); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); Vector128 result = source * storedAlpha; - Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); - return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); } /// - /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// Converts unassociated scaled vectors to associated scaled components. /// /// The unassociated vectors. - /// Whether the source contains scaled vectors. - /// The associated scaled vectors. + /// The associated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 Associate(Vector256 source, bool scaled) + private static Vector256 Associate(Vector256 source) { - Vector256 zero = Vector256.Zero; - Vector256 one = Vector256.One; - - if (!scaled) - { - // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. - source = ToScaled(source); - } - - source = Vector256.Min(Vector256.Max(source, zero), one); + source = ClampUnit(source); Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); - Vector256 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector256.Create(2F)) - one) + one) / Vector256.Create(2F); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); Vector256 result = source * storedAlpha; - Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); - return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); } /// - /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// Converts unassociated scaled vectors to associated scaled components. /// /// The unassociated vectors. - /// Whether the source contains scaled vectors. - /// The associated scaled vectors. + /// The associated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 Associate(Vector512 source, bool scaled) + private static Vector512 Associate(Vector512 source) { - Vector512 zero = Vector512.Zero; - Vector512 one = Vector512.One; - - if (!scaled) - { - // Binary16-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. - source = ToScaled(source); - } - - source = Vector512.Min(Vector512.Max(source, zero), one); + source = ClampUnit(source); Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); - Vector512 storedAlpha = (HalfTypeHelper.RoundToHalf((alpha * Vector512.Create(2F)) - one) + one) / Vector512.Create(2F); + 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 native or scaled vectors with the scaled alpha values the destination stores. + /// Reassociates an associated scaled vector after alpha quantization. /// /// The associated vectors. - /// Whether the source contains scaled vectors. - /// The reassociated scaled vectors. + /// The reassociated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 Reassociate(Vector128 source, bool scaled) + private static Vector128 Reassociate(Vector128 source) { Vector128 zero = Vector128.Zero; - Vector128 one = Vector128.One; - - if (!scaled) - { - // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. - source = ToScaled(source); - } - Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); - Vector128 nativeAlpha = (alpha * Vector128.Create(2F)) - one; - Vector128 clampedNativeAlpha = Vector128.Min(Vector128.Max(nativeAlpha, -one), one); - - // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. - nativeAlpha = Vector128.ConditionalSelect(Vector128.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); - - // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. - Vector128 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector128.Create(2F); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); Vector128 result = source * (storedAlpha / alpha); - Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); - result = Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + 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 native or scaled vectors with the scaled alpha values the destination stores. + /// Reassociates associated scaled vectors after alpha quantization. /// /// The associated vectors. - /// Whether the source contains scaled vectors. - /// The reassociated scaled vectors. + /// The reassociated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 Reassociate(Vector256 source, bool scaled) + private static Vector256 Reassociate(Vector256 source) { Vector256 zero = Vector256.Zero; - Vector256 one = Vector256.One; - - if (!scaled) - { - // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. - source = ToScaled(source); - } - Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); - Vector256 nativeAlpha = (alpha * Vector256.Create(2F)) - one; - Vector256 clampedNativeAlpha = Vector256.Min(Vector256.Max(nativeAlpha, -one), one); - - // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. - nativeAlpha = Vector256.ConditionalSelect(Vector256.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); - - // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. - Vector256 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector256.Create(2F); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); 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.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 native or scaled vectors with the scaled alpha values the destination stores. + /// Reassociates associated scaled vectors after alpha quantization. /// /// The associated vectors. - /// Whether the source contains scaled vectors. - /// The reassociated scaled vectors. + /// The reassociated vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 Reassociate(Vector512 source, bool scaled) + private static Vector512 Reassociate(Vector512 source) { Vector512 zero = Vector512.Zero; - Vector512 one = Vector512.One; - - if (!scaled) - { - // Convert every component together so RGB and alpha enter reassociation in the same scaled coordinate system. - source = ToScaled(source); - } - Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); - Vector512 nativeAlpha = (alpha * Vector512.Create(2F)) - one; - Vector512 clampedNativeAlpha = Vector512.Min(Vector512.Max(nativeAlpha, -one), one); - - // Hardware Min/Max replace NaN on .NET 8, whereas the scalar clamp preserves it. Restore those lanes before half quantization. - nativeAlpha = Vector512.ConditionalSelect(Vector512.Equals(nativeAlpha, nativeAlpha), clampedNativeAlpha, nativeAlpha); - - // Clamp before binary16 quantization because the scaled alpha contract cannot represent opacity outside [0, 1]. - Vector512 storedAlpha = (HalfTypeHelper.RoundToHalf(nativeAlpha) + one) / Vector512.Create(2F); + 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); @@ -710,51 +290,86 @@ public partial struct HalfVector4P } /// - /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// 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 lower scaled values. - /// The upper scaled values. - /// The packed binary16 values. + /// The vectors to clamp. + /// The clamped vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 PackScaled(Vector128 lower, Vector128 upper) + private static Vector128 ClampUnit(Vector128 source) { - lower *= Vector128.Create(2F); - lower -= Vector128.One; - upper *= Vector128.Create(2F); - upper -= Vector128.One; - return HalfTypeHelper.Pack(lower, upper); + Vector128 clamped = Vector128.Min(Vector128.Max(source, Vector128.Zero), Vector128.One); + return Vector128.ConditionalSelect(Vector128.Equals(source, source), clamped, source); } /// - /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// Clamps vectors to the scaled color range while preserving NaN lanes. /// - /// The lower scaled values. - /// The upper scaled values. - /// The packed binary16 values. + /// The vectors to clamp. + /// The clamped vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PackScaled(Vector256 lower, Vector256 upper) + private static Vector256 ClampUnit(Vector256 source) { - lower *= Vector256.Create(2F); - lower -= Vector256.One; - upper *= Vector256.Create(2F); - upper -= Vector256.One; - return HalfTypeHelper.Pack(lower, upper); + Vector256 clamped = Vector256.Min(Vector256.Max(source, Vector256.Zero), Vector256.One); + return Vector256.ConditionalSelect(Vector256.Equals(source, source), clamped, source); } /// - /// Converts two scaled vectors to binary16 storage while preserving scalar operation order. + /// Clamps vectors to the scaled color range while preserving NaN lanes. /// - /// The lower scaled values. - /// The upper scaled values. - /// The packed binary16 values. + /// The vectors to clamp. + /// The clamped vectors. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 PackScaled(Vector512 lower, Vector512 upper) + 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) { - lower *= Vector512.Create(2F); - lower -= Vector512.One; - upper *= Vector512.Create(2F); - upper -= Vector512.One; - return HalfTypeHelper.Pack(lower, upper); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs index 509803f50..de2f8f17b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs @@ -20,6 +20,8 @@ public partial struct NormalizedByte4P /// internal class PixelOperations : AssociatedAlphaPixelOperations { + private const byte RestorePackedPixelOrder = 0b_11_01_10_00; + /// protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) { @@ -107,27 +109,33 @@ public partial struct NormalizedByte4P int componentsPerPixel = Vector128.Count; int i = 0; - if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) + // 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(); - Vector512 integers = Avx512F.ConvertToVector512Int32(packed); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Avx512F.ConvertToVector512Single(integers), scaled); + 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 (Avx2.IsSupported) + 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))); - Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsSByte()); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Avx.ConvertToVector256Single(integers), scaled); + 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); } } @@ -162,6 +170,9 @@ public partial struct NormalizedByte4P [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. @@ -180,6 +191,9 @@ public partial struct NormalizedByte4P [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. @@ -198,6 +212,9 @@ public partial struct NormalizedByte4P [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. @@ -222,27 +239,33 @@ public partial struct NormalizedByte4P int componentsPerPixel = Vector128.Count; int i = 0; - if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) + // 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(); - Vector512 integers = Avx512F.ConvertToVector512Int32(packed); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx512F.ConvertToVector512Single(integers), scaled); + 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 (Avx2.IsSupported) + 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))); - Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsSByte()); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx.ConvertToVector256Single(integers), scaled); + 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); } } @@ -277,6 +300,8 @@ public partial struct NormalizedByte4P [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(); @@ -297,6 +322,8 @@ public partial struct NormalizedByte4P [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(); @@ -317,6 +344,8 @@ public partial struct NormalizedByte4P [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(); @@ -663,14 +692,23 @@ public partial struct NormalizedByte4P ref NormalizedByte4P destinationBase = ref MemoryMarshal.GetReference(destination); int i = 0; - if (Vector512.IsHardwareAccelerated && Avx512F.IsSupported) + 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)); - Vector128 packed = Avx512F.ConvertToVector128SByteWithSaturation(ConvertToPackedInt32(vector, scaled)); + 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; } } 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 000000000..f3c01e082 --- /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 000000000..825ebeec9 --- /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/Short2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs index 7520b33e0..4d6b066ad 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs @@ -16,8 +16,8 @@ public partial struct Short2 /// internal class PixelOperations : PixelOperations { - private static readonly Vector4 NativeOffset = new(MaxPos, MaxPos, 0F, 0F); - private static readonly Vector4 NativeDivisor = new(MaxPos * 2F, MaxPos * 2F, 1F, 1F); + 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. @@ -32,7 +32,7 @@ public partial struct Short2 { // 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, NativeDivisor); + 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 d9108be41..83c3ce053 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs @@ -17,8 +17,8 @@ public partial struct Short4 /// internal class PixelOperations : PixelOperations { - private static readonly Vector4 NativeOffset = new(MaxPos); - private static readonly Vector4 NativeMagnitude = new(MaxPos * 2F); + private static readonly Vector4 NativeOffset = new(-MinNeg); + private static readonly Vector4 NativeMagnitude = new(Range); /// protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index b84e0bf78..a88823069 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -7,11 +7,12 @@ using System.Runtime.CompilerServices; 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); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index 79913b499..340e24777 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -7,12 +7,13 @@ using System.Runtime.CompilerServices; 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); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index a9bc1e062..300f2357f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -8,11 +8,12 @@ using System.Runtime.InteropServices; 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 { diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs new file mode 100644 index 000000000..573bfca55 --- /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 000000000..75fe4907c --- /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/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 59c6b4e0a..d6ab2ac1a 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 partial struct Short2 : IPixel, IPackedVector public readonly Vector4 ToScaledVector4() { Vector2 scaled = this.ToVector2(); - scaled += new Vector2(32767f); - scaled /= 65534F; + scaled -= Min; + scaled /= Range; return new Vector4(scaled, 0f, 1f); } @@ -129,9 +134,9 @@ public partial struct Short2 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short2 FromAssociatedVector4(Vector4 source) { - // Only the stored color components use the signed native encoding; W remains the normalized source alpha. - source.X = (source.X + MaxPos) / (MaxPos * 2F); - source.Y = (source.Y + MaxPos) / (MaxPos * 2F); + // 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); } @@ -139,8 +144,8 @@ public partial struct Short2 : IPixel, IPackedVector [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 a128668fd..fc3d3367d 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 partial struct Short4 : IPixel, IPackedVector public readonly Vector4 ToScaledVector4() { Vector4 scaled = this.ToVector4(); - scaled += new Vector4(32767f); - scaled /= 65534f; + scaled -= Min; + scaled /= Range; return scaled; } @@ -122,9 +126,9 @@ public partial struct Short4 : IPixel, IPackedVector { Vector4 vector = this.ToAssociatedScaledVector4(); - // Native components use an affine signed encoding, so direct multiplication would use the wrong zero point. - vector *= MaxPos * 2F; - vector -= new Vector4(MaxPos); + // Native components use the full asymmetric signed range, so scaled zero must map to -32768 rather than -32767. + vector *= Range; + vector += Min; return vector; } @@ -148,9 +152,9 @@ public partial struct Short4 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short4 FromAssociatedVector4(Vector4 source) { - // Map the affine signed native encoding to logical [0, 1] space before unassociating. - source += new Vector4(MaxPos); - source /= MaxPos * 2F; + // Map the full signed native encoding to scaled [0, 1] space before unassociating. + source -= Min; + source /= Range; return FromAssociatedScaledVector4(source); } @@ -158,8 +162,8 @@ public partial struct Short4 : IPixel, IPackedVector [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/Utils/SignedShort4PixelOperations.cs b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs index 154d64389..4bf8c5e26 100644 --- a/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs @@ -17,7 +17,9 @@ internal static class SignedShort4PixelOperations { private const byte RestorePackedPixelOrder = 0b_11_01_10_00; private const float ShortMaximum = short.MaxValue; - private const float ShortMagnitude = ShortMaximum * 2F; + 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. @@ -79,11 +81,14 @@ internal static class SignedShort4PixelOperations 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 /= ShortMagnitude; + vector /= SignedNormalizedMagnitude; } else { @@ -92,8 +97,9 @@ internal static class SignedShort4PixelOperations } else if (scaled) { - vector += new Vector4(ShortMaximum); - vector /= ShortMagnitude; + // 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; @@ -182,8 +188,8 @@ internal static class SignedShort4PixelOperations { if (scaled) { - vector *= ShortMagnitude; - vector -= new Vector4(ShortMaximum); + vector *= SignedIntegerRange; + vector += new Vector4(SignedIntegerMinimum); } vector = Numerics.Clamp(vector, new Vector4(short.MinValue), new Vector4(short.MaxValue)); @@ -209,11 +215,14 @@ internal static class SignedShort4PixelOperations { 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(ShortMagnitude); + source /= Vector512.Create(SignedNormalizedMagnitude); } else { @@ -222,8 +231,8 @@ internal static class SignedShort4PixelOperations } else if (scaled) { - source += Vector512.Create(ShortMaximum); - source /= Vector512.Create(ShortMagnitude); + source -= Vector512.Create(SignedIntegerMinimum); + source /= Vector512.Create(SignedIntegerRange); } return source; @@ -241,11 +250,14 @@ internal static class SignedShort4PixelOperations { 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(ShortMagnitude); + source /= Vector256.Create(SignedNormalizedMagnitude); } else { @@ -254,8 +266,8 @@ internal static class SignedShort4PixelOperations } else if (scaled) { - source += Vector256.Create(ShortMaximum); - source /= Vector256.Create(ShortMagnitude); + source -= Vector256.Create(SignedIntegerMinimum); + source /= Vector256.Create(SignedIntegerRange); } return source; @@ -273,11 +285,14 @@ internal static class SignedShort4PixelOperations { 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(ShortMagnitude); + source /= Vector128.Create(SignedNormalizedMagnitude); } else { @@ -286,8 +301,8 @@ internal static class SignedShort4PixelOperations } else if (scaled) { - source += Vector128.Create(ShortMaximum); - source /= Vector128.Create(ShortMagnitude); + source -= Vector128.Create(SignedIntegerMinimum); + source /= Vector128.Create(SignedIntegerRange); } return source; @@ -318,8 +333,8 @@ internal static class SignedShort4PixelOperations { if (scaled) { - source *= Vector512.Create(ShortMagnitude); - source -= Vector512.Create(ShortMaximum); + source *= Vector512.Create(SignedIntegerRange); + source += Vector512.Create(SignedIntegerMinimum); } source = Vector512.Min(Vector512.Max(source, Vector512.Create((float)short.MinValue)), Vector512.Create((float)short.MaxValue)); @@ -353,8 +368,8 @@ internal static class SignedShort4PixelOperations { if (scaled) { - source *= Vector256.Create(ShortMagnitude); - source -= Vector256.Create(ShortMaximum); + source *= Vector256.Create(SignedIntegerRange); + source += Vector256.Create(SignedIntegerMinimum); } source = Vector256.Min(Vector256.Max(source, Vector256.Create((float)short.MinValue)), Vector256.Create((float)short.MaxValue)); @@ -388,8 +403,8 @@ internal static class SignedShort4PixelOperations { if (scaled) { - source *= Vector128.Create(ShortMagnitude); - source -= Vector128.Create(ShortMaximum); + source *= Vector128.Create(SignedIntegerRange); + source += Vector128.Create(SignedIntegerMinimum); } source = Vector128.Min(Vector128.Max(source, Vector128.Create((float)short.MinValue)), Vector128.Create((float)short.MaxValue)); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs index dd38e4118..b47779df8 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -5,7 +5,6 @@ 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; @@ -51,27 +50,33 @@ internal static partial class Vector4Converters int componentsPerPixel = Vector128.Count; int i = 0; - if (Avx512F.IsSupported) + // 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)); - Vector512 integers = Avx512F.ConvertToVector512Int32(packed); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx512F.ConvertToVector512Single(integers)); + 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 (Avx2.IsSupported) + 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))); - Vector256 integers = Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsByte()); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Avx.ConvertToVector256Single(integers)); + 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)); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs index f63f85344..07a5c4dcc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -121,6 +121,17 @@ public class NormalizedByte4PTests : AssociatedAlphaPixelTests 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() { @@ -285,14 +296,7 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests [Fact] public void ColorRoundTripDoesNotIntroduceAdditionalAssociationLoss() { - const ulong zeroComponent = 0xBC00; - - // This is the first valid Half component/alpha pair for which an unassociate/reassociate round trip - // selects the adjacent component value instead of the direct scaled-vector conversion result. - HalfVector4P source = new() - { - PackedValue = 0x8BF5 | (zeroComponent << 16) | (zeroComponent << 32) | (0x05DFUL << 48) - }; + 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]; @@ -421,9 +425,8 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests 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); - // HalfVector4's native domain is [-1, 1], so the native entry points need the affine encoding of the common scaled values. - unassociatedNative[i] = (unassociatedScaled[i] * 2F) - Vector4.One; - associatedNative[i] = (associatedScaled[i] * 2F) - Vector4.One; + 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]); @@ -824,8 +827,10 @@ public class AssociatedToUnassociatedPackedPixelConversionTests const int pairCount = 65536; const int channelCount = 3; - // HalfVector4 maps scaled zero to native -1, whose IEEE 754 binary16 representation is 0xBC00. - const ulong zeroComponentBits = 0xBC00; + 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]; @@ -833,18 +838,16 @@ public class AssociatedToUnassociatedPackedPixelConversionTests for (int alpha = 0; alpha <= byte.MaxValue; alpha++) { - // HalfVector4 stores normalized values over -1 through 1. Association must use the alpha value - // recovered from the destination half, rather than the higher-precision source 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); - float nativeAlpha = (normalizedAlpha * 2F) - 1F; - ushort alphaBits = BitConverter.HalfToUInt16Bits((Half)nativeAlpha); - float destinationAlpha = ((float)BitConverter.UInt16BitsToHalf(alphaBits) + 1F) * .5F; + 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 * 2F) - 1F)); + ushort associatedBits = BitConverter.HalfToUInt16Bits((Half)((associated * finiteRange) + finiteMinimum)); ulong alphaPacked = (ulong)alphaBits << 48; source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); @@ -1184,6 +1187,9 @@ public class AssociatedDestinationAlphaQuantizationTests [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]; @@ -1202,14 +1208,14 @@ public class AssociatedDestinationAlphaQuantizationTests for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) { - float nativeAlpha = ((alpha / (float)ushort.MaxValue) * 2F) - 1F; - ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)nativeAlpha); - float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) + 1F) / 2F; + 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 * 2F) - 1F; - ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)associatedRed); + 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); @@ -1221,6 +1227,45 @@ public class AssociatedDestinationAlphaQuantizationTests } } + [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(); @@ -1239,6 +1284,9 @@ public class AssociatedDestinationAlphaQuantizationTests [Fact] public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + [Fact] + public void ColorToRgbaHalfPUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + /// /// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points. /// diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 9366c51c9..f8f127625 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -26,45 +26,57 @@ public class HalfSingleTests 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() + { + 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]; - // assert - Assert.Equal(expected, actual); + 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 c5a89df1e..8adc38bd3 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -25,19 +25,21 @@ public class HalfVector2Tests 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 +49,8 @@ public class HalfVector2Tests 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 +60,25 @@ public class HalfVector2Tests Assert.Equal(expected, actual); } + [Fact] + public void HalfVector2_BulkScaledConversionsCoverFiniteRange() + { + 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 16c78a23d..257eceb9f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -33,30 +33,32 @@ public class HalfVector4Tests 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 +68,26 @@ public class HalfVector4Tests Assert.Equal(expected, actual); } + [Fact] + public void HalfVector4_BulkScaledConversionsCoverFiniteRange() + { + 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 ffbddb139..954d388c0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -29,6 +29,37 @@ public class NormalizedByte2Tests Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedByte2_MinimumStorageCodeDecodesAsNegativeOne() + { + 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 5a025f6c4..0fdb88cae 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -60,6 +60,34 @@ public class NormalizedByte4Tests Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedByte4_MinimumStorageCodeDecodesAsNegativeOne() + { + 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 be7b39052..e9f97d27f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -33,6 +33,37 @@ public class NormalizedShort2Tests Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedShort2_MinimumStorageCodeDecodesAsNegativeOne() + { + 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 281ae7ee5..9ed3456de 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -61,6 +61,34 @@ public class NormalizedShort4Tests Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedShort4_MinimumStorageCodeDecodesAsNegativeOne() + { + 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 index 036f1cd71..c6afe55b5 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs @@ -308,7 +308,7 @@ public abstract class PixelAlphaRepresentationTests } /// -/// Verifies alpha conversion for formats whose native vector space differs from scaled color space. +/// Verifies alpha conversion for formats with distinct native vector contracts. /// [Trait("Category", "PixelFormats")] public class AffineNativeAlphaRepresentationTests @@ -321,7 +321,7 @@ public class AffineNativeAlphaRepresentationTests [Fact] public void Short4NativeConversionsUseScaledAlpha() - => AssertNativeConversions(static vector => (vector * 65534F) - new Vector4(32767F)); + => AssertNativeConversions(static vector => (vector * ushort.MaxValue) - new Vector4(32768F)); [Fact] public void NormalizedByte4NativeConversionsUseScaledAlpha() @@ -337,19 +337,19 @@ public class AffineNativeAlphaRepresentationTests [Fact] public void HalfVector4NativeConversionsUseScaledAlpha() - => AssertNativeConversions(static vector => (vector * 2F) - Vector4.One); + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); [Fact] public void HalfVector4PNativeConversionsUseScaledAlpha() - => AssertNativeConversions(static vector => (vector * 2F) - Vector4.One); + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); [Fact] public void HalfSingleFromAssociatedNativeVectorUsesScaledAlpha() - => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, 0F, 0F, vector.W)); + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, 0F, 0F, vector.W)); [Fact] public void HalfVector2FromAssociatedNativeVectorUsesScaledAlpha() - => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 0F, vector.W)); + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, (vector.Y * 131008F) - 65504F, 0F, vector.W)); [Fact] public void NormalizedByte2FromAssociatedNativeVectorUsesScaledAlpha() @@ -361,7 +361,7 @@ public class AffineNativeAlphaRepresentationTests [Fact] public void Short2FromAssociatedNativeVectorUsesScaledAlpha() - => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 65534F) - 32767F, (vector.Y * 65534F) - 32767F, 0F, vector.W)); + => 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 @@ -486,6 +486,8 @@ 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 { } 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 f287a6995..3747b41ee 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs @@ -492,6 +492,36 @@ public partial class PixelOperationsTests } } + 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 43dec7937..e4a7e881e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -29,6 +29,7 @@ using Xunit.Abstractions; "Rgba1010102", "Rgba32", "Rgba64", + "RgbaHalf", "RgbaVector", "Short4" ]; @@ -40,7 +41,8 @@ using Xunit.Abstractions; "Bgra32P", "HalfVector4P", "NormalizedByte4P", - "Rgba32P" + "Rgba32P", + "RgbaHalfP" ]; private static readonly string[] CommonPixelTypes = @@ -77,6 +79,8 @@ using Xunit.Abstractions; "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 7be839e72..c569919f5 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -102,6 +102,35 @@ public abstract class PixelOperationsTests : MeasureFixture 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() { @@ -171,23 +200,8 @@ public abstract class PixelOperationsTests : MeasureFixture } } - void ExpectedAction(ref Vector4 v) - { - if (this.HasAssociatedAlpha) - { - Numerics.UnPremultiply(ref 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, @@ -274,19 +288,8 @@ public abstract class PixelOperationsTests : MeasureFixture Numerics.Premultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.UnPremultiply(ref 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, true); TestOperation( source, @@ -349,6 +352,8 @@ public abstract class PixelOperationsTests : MeasureFixture new TestPixel(), new TestPixel(), new TestPixel(), + new TestPixel(), + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs new file mode 100644 index 000000000..9b22a9f8c --- /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 f23da0c7a..c0ee816f1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -51,33 +51,39 @@ public class Short2Tests [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; - - // act - Vector4 scaled = short2.ToScaledVector4(); - Short2 pixel = Short2.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; + Assert.Equal(0x80008000U, Short2.FromScaledVector4(new Vector4(0F, 0F, 0F, 1F)).PackedValue); + Assert.Equal(0x7FFF7FFFU, Short2.FromScaledVector4(Vector4.One).PackedValue); + } - // assert - Assert.Equal(expected, actual); + [Fact] + public void Short2_BulkScaledConversionsCoverFullSignedRange() + { + 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 819ff0e1e..68f774f15 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -38,32 +38,39 @@ public class Short4Tests [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; - - // act - Short4 pixel = Short4.FromScaledVector4(scaled); + Assert.Equal(0x8000800080008000UL, Short4.FromScaledVector4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, Short4.FromScaledVector4(Vector4.One).PackedValue); + } - // assert - Assert.Equal((ulong)expected, pixel.PackedValue); + [Fact] + public void Short4_BulkScaledConversionsCoverFullSignedRange() + { + 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/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 135212400..d5f7f69d9 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -43,13 +43,14 @@ public class BrightnessTest : BaseImageOperationsExtensionTest 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))); From 644cd49fc0fca214604d5f3f3a34a148f75c6520 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 15:44:32 +1000 Subject: [PATCH 208/240] Update ExrDecoderCore.cs --- src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs index 2cb382de1..abe3ae439 100644 --- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs +++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs @@ -649,7 +649,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore 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 From ba4d1df1e1886081f0ef4918237a0fb5a6c9d4fe Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 15:52:50 +1000 Subject: [PATCH 209/240] Potential fix for pull request finding 'Equality check on floating point values' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../Utils/Vector4Converters.AssociatedRgbaCompatible.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs index b47779df8..05fd97ab4 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -371,8 +371,10 @@ internal static partial class Vector4Converters float byteAlpha = alpha * byte.MaxValue; float storedAlpha = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); + byte quantizedAlpha = (byte)storedAlpha; + byte sourceAlphaByte = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); - if (byteAlpha == storedAlpha) + if (sourceAlphaByte == quantizedAlpha) { // Quantization leaves alpha unchanged, so RGB is already associated correctly. Avoiding division preserves exact byte midpoints produced by scaling stored components. source *= byte.MaxValue; From 42f6fb6276b2ce030833ce0eec06df3659964fce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 15:54:14 +1000 Subject: [PATCH 210/240] Revert "Potential fix for pull request finding 'Equality check on floating point values'" This reverts commit ba4d1df1e1886081f0ef4918237a0fb5a6c9d4fe. --- .../Utils/Vector4Converters.AssociatedRgbaCompatible.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs index 05fd97ab4..b47779df8 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -371,10 +371,8 @@ internal static partial class Vector4Converters float byteAlpha = alpha * byte.MaxValue; float storedAlpha = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); - byte quantizedAlpha = (byte)storedAlpha; - byte sourceAlphaByte = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); - if (sourceAlphaByte == quantizedAlpha) + 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; From c1449ebeb2f5c8fcf3b5282a0624d413c1230835 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 16:00:10 +1000 Subject: [PATCH 211/240] Update AssociatedAlphaPixelTests.cs --- .../ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs index 07a5c4dcc..e32cfec18 100644 --- a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -354,7 +354,7 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests 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); + Span components = MemoryMarshal.Cast(source.AsSpan()); int index = 0; for (int bits = 0; bits <= ushort.MaxValue; bits++) From e1c3013f96032e184ee35915809d09d4fea4819b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2026 16:47:47 +1000 Subject: [PATCH 212/240] Fix bulk RGBA-compatible conversion bounds Slice the destination span to source length in `Vector4Converters.RgbaCompatible.FromVector4` so SIMD/scalar tails never process spare destination capacity. Expanded pixel-format tests to run across hardware intrinsic configurations, added dedicated RGB24/BGR24 bulk conversion coverage with sentinel checks for out-of-range writes, and relaxed associated-alpha half packing assertions to treat NaN payload differences as valid IEEE behavior. --- .../Utils/Vector4Converters.RgbaCompatible.cs | 4 ++ .../PixelFormats/AssociatedAlphaPixelTests.cs | 20 ++++++- .../PixelFormats/HalfSingleTests.cs | 8 ++- .../PixelFormats/HalfVector2Tests.cs | 8 ++- .../PixelFormats/HalfVector4Tests.cs | 8 ++- .../PixelFormats/NormalizedByte2Tests.cs | 8 ++- .../PixelFormats/NormalizedByte4Tests.cs | 8 ++- .../PixelFormats/NormalizedShort2Tests.cs | 8 ++- .../PixelFormats/NormalizedShort4Tests.cs | 8 ++- .../PixelAlphaRepresentationTests.cs | 53 +++++++++++++++++++ .../PixelFormats/Short2Tests.cs | 8 ++- .../PixelFormats/Short4Tests.cs | 8 ++- 12 files changed, 139 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index c08b79f1e..886732eb9 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -100,6 +100,10 @@ internal static partial class Vector4Converters 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/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs index e32cfec18..c115850fc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -395,7 +395,25 @@ public class HalfVector4PTests : AssociatedAlphaPixelTests PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actual, PixelConversionModifiers.None); - Assert.Equal(expected, actual); + 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() diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index f8f127625..3e06f0471 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; @@ -55,7 +56,12 @@ public class HalfSingleTests } [Fact] - public void HalfSingle_BulkScaledConversionsCoverFiniteRange() + public void HalfSingle_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfSingleBulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + 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)]; diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index 8adc38bd3..1fde34a82 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; @@ -61,7 +62,12 @@ public class HalfVector2Tests } [Fact] - public void HalfVector2_BulkScaledConversionsCoverFiniteRange() + 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]; diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 257eceb9f..75130c144 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; @@ -69,7 +70,12 @@ public class HalfVector4Tests } [Fact] - public void HalfVector4_BulkScaledConversionsCoverFiniteRange() + 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 }; diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index 954d388c0..e6be539c9 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; @@ -30,7 +31,12 @@ public class NormalizedByte2Tests } [Fact] - public void NormalizedByte2_MinimumStorageCodeDecodesAsNegativeOne() + 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); diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 0fdb88cae..c2a0047d2 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; @@ -61,7 +62,12 @@ public class NormalizedByte4Tests } [Fact] - public void NormalizedByte4_MinimumStorageCodeDecodesAsNegativeOne() + public void NormalizedByte4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne() { NormalizedByte4 pixel = new() { PackedValue = 0x80808080 }; diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index e9f97d27f..9cb3aee50 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; @@ -34,7 +35,12 @@ public class NormalizedShort2Tests } [Fact] - public void NormalizedShort2_MinimumStorageCodeDecodesAsNegativeOne() + 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); diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index 9ed3456de..c317586e4 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; @@ -62,7 +63,12 @@ public class NormalizedShort4Tests } [Fact] - public void NormalizedShort4_MinimumStorageCodeDecodesAsNegativeOne() + public void NormalizedShort4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne() { NormalizedShort4 pixel = new() { PackedValue = 0x8000800080008000 }; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs index c6afe55b5..2cc26e76c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs @@ -5,6 +5,7 @@ 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; @@ -307,6 +308,58 @@ public abstract class PixelAlphaRepresentationTests 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. /// diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index c0ee816f1..86fc82830 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; @@ -63,7 +64,12 @@ public class Short2Tests } [Fact] - public void Short2_BulkScaledConversionsCoverFullSignedRange() + public void Short2_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort2BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertShort2BulkScaledConversionsCoverFullSignedRange() { const int length = 17; Short2[] source = new Short2[length]; diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 68f774f15..c8502462d 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; @@ -50,7 +51,12 @@ public class Short4Tests } [Fact] - public void Short4_BulkScaledConversionsCoverFullSignedRange() + public void Short4_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort4BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertShort4BulkScaledConversionsCoverFullSignedRange() { const int length = 17; Short4[] source = new Short4[length]; From c54293cdfb2ca74bc9b9b3318ccd2f743cf9cde6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 09:42:36 +0000 Subject: [PATCH 213/240] Bump actions/setup-dotnet from 5 to 6 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2156982f8..634b904ae 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -166,14 +166,14 @@ jobs: - name: DotNet Setup if: ${{ matrix.options.sdk-preview != true }} - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 8.0.x - name: DotNet Setup Preview if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 10.0.x diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 0e55bbe7d..00f489b40 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -67,7 +67,7 @@ jobs: restore-keys: ${{ runner.os }}-nuget- - name: DotNet Setup - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 8.0.x From 3d89f941d418c43264a44d0d5d21d75d84b7e1d6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jul 2026 17:37:58 +1000 Subject: [PATCH 214/240] Fix degeneracy check for resize transforms Use exact zero determinant checks in `TransformUtilities.IsDegenerate` for `Matrix3x2` and `Matrix4x4` instead of epsilon-based near-zero detection, preventing valid transforms from being treated as degenerate during extreme downscales. Added a regression test for issue #3156 to verify resizing large square images (1024/2048) to 1x1 succeeds and preserves the expected pixel. --- .../Processors/Transforms/TransformUtilities.cs | 8 ++------ .../Processors/Transforms/ResizeTests.cs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs index 5c7de1710..b1d7b61ee 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -19,7 +19,7 @@ internal static class TransformUtilities /// /// The transform matrix. public static bool IsDegenerate(Matrix3x2 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + => IsNaN(matrix) || matrix.GetDeterminant() == 0F; /// /// Returns a value that indicates whether the specified matrix is degenerate @@ -28,11 +28,7 @@ internal static class TransformUtilities /// /// The transform matrix. public static bool IsDegenerate(Matrix4x4 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsZero(float a) - => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; + => IsNaN(matrix) || matrix.GetDeterminant() == 0F; /// /// Returns a value that indicates whether the specified matrix contains any values diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 7389c4b89..4214cc5e0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -699,6 +699,20 @@ public class ResizeTests appendSourceFileOrDescription: false); } + [Theory] + [InlineData(1024)] + [InlineData(2048)] + public void Issue3156_CanResizeLargeSquareToSinglePixel(int length) + { + Rgba32 white = Color.White.ToPixel(); + using Image image = new(length, length, white); + + image.Mutate(x => x.Resize(1, 1)); + + Assert.Equal(new Size(1, 1), image.Size); + Assert.Equal(white, image[0, 0]); + } + [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ResizeUpdatesSubject(TestImageProvider provider) From dfdc0734e34899695c170db89ebc2d522dac455c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jul 2026 19:10:07 +1000 Subject: [PATCH 215/240] Validate CgBI chunk order in PNG decoder Handle CgBI chunks consistently when they appear after IHDR by routing both identify/decode paths through a new `ReadCgbiChunk()` method that immediately validates already-read headers. This prevents invalid CgBI images from bypassing compatibility checks when chunk order is unusual. Added PNG tests that reorder the first two chunks to confirm incompatible CgBI headers now throw in both identify and decode flows. Also renames `ZlibInflateStream` to `ZlibInflateReader` and updates EXR/PNG/TIFF call sites for clearer intent. --- ...bInflateStream.cs => ZlibInflateReader.cs} | 6 +- .../Exr/Compression/ExrBaseDecompressor.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 23 ++++++-- .../Decompressors/DeflateTiffCompression.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 55 +++++++++++++++++++ 5 files changed, 79 insertions(+), 9 deletions(-) rename src/ImageSharp/Compression/Zlib/{ZlibInflateStream.cs => ZlibInflateReader.cs} (95%) diff --git a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateReader.cs similarity index 95% rename from src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs rename to src/ImageSharp/Compression/Zlib/ZlibInflateReader.cs index 11f34dac8..de69717da 100644 --- a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibInflateReader.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib; /// over the remaining DEFLATE payload. The /// Adler-32 trailer is not validated. /// -internal sealed class ZlibInflateStream : IDisposable +internal sealed class ZlibInflateReader : IDisposable { /// /// Used to read the Adler-32 and Crc-32 checksums. @@ -23,10 +23,10 @@ internal sealed class ZlibInflateStream : IDisposable private readonly ChunkedReadStream segmentStream; - public ZlibInflateStream(BufferedReadStream innerStream) + public ZlibInflateReader(BufferedReadStream innerStream) => this.segmentStream = new ChunkedReadStream(innerStream); - public ZlibInflateStream(BufferedReadStream innerStream, Func getData) + public ZlibInflateReader(BufferedReadStream innerStream, Func getData) => this.segmentStream = new ChunkedReadStream(innerStream, getData); /// diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index 1b2d95ba5..f598955f4 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -45,7 +45,7 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression protected static int UndoZipCompression(BufferedReadStream stream, uint compressedBytes, Span uncompressed, uint uncompressedBytes) { long pos = stream.Position; - using ZlibInflateStream inflateStream = new( + using ZlibInflateReader inflateStream = new( stream, () => { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f3e2bbdbe..28f9a989b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -321,7 +321,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore case PngChunkType.End: goto EOF; case PngChunkType.ProprietaryApple: - this.isCgbi = true; + this.ReadCgbiChunk(); break; } } @@ -525,7 +525,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore goto EOF; case PngChunkType.ProprietaryApple: - this.isCgbi = true; + this.ReadCgbiChunk(); break; default: @@ -788,7 +788,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore return; } - using ZlibInflateStream inflateStream = new(this.currentStream, getData); + using ZlibInflateReader inflateStream = new(this.currentStream, getData); if (!inflateStream.AllocateNewBytes(chunkLength, !this.hasImageData)) { return; @@ -1458,6 +1458,21 @@ internal sealed class PngDecoderCore : ImageDecoderCore } } + /// + /// Marks the image as CgBI and validates a header that has already been read. + /// + private void ReadCgbiChunk() + { + this.isCgbi = true; + + // Although Apple's pngcrush normally writes CgBI before IHDR, accepting the + // reverse order must apply the same compatibility validation. + if (!Equals(this.header, default(PngHeader))) + { + ThrowIfInvalidCgbiContent(this.header); + } + } + private static void ThrowIfInvalidCgbiContent(in PngHeader header) { if (header.BitDepth != 8 || (header.ColorType is not PngColorType.Rgb and not PngColorType.RgbWithAlpha)) @@ -1952,7 +1967,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore using MemoryStream memoryStreamOutput = new(compressedData.Length); using UnmanagedMemoryStream memoryStreamInput = new(compressedDataBase, compressedData.Length); using BufferedReadStream bufferedStream = new(this.configuration, memoryStreamInput); - using ZlibInflateStream inflateStream = new(bufferedStream); + using ZlibInflateReader inflateStream = new(bufferedStream); Span destUncompressedData = destBuffer.GetSpan(); if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 4e176f28d..d3b65c537 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -54,7 +54,7 @@ internal sealed class DeflateTiffCompression : TiffBaseDecompressor protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { long pos = stream.Position; - using (ZlibInflateStream deframeStream = new( + using (ZlibInflateReader deframeStream = new( stream, () => { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 2fbbe695e..88ed512d1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; using System.Runtime.Intrinsics.X86; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; @@ -789,6 +790,60 @@ public partial class PngDecoderTests Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); } + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Identify_CgBI_AfterHeader_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + byte[] reordered = MoveFirstPngChunkAfterSecond(testFile.Bytes); + using MemoryStream stream = new(reordered, false); + + InvalidImageContentException ex = Assert.Throws(() => Image.Identify(stream)); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Decode_CgBI_AfterHeader_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + byte[] reordered = MoveFirstPngChunkAfterSecond(testFile.Bytes); + using MemoryStream stream = new(reordered, false); + + InvalidImageContentException ex = Assert.Throws( + () => { using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, stream); }); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + /// + /// Moves the first PNG chunk after the second while preserving each chunk's data and CRC. + /// + /// A PNG whose first two chunks should be exchanged. + /// A copy of the PNG with its first two chunks exchanged. + private static byte[] MoveFirstPngChunkAfterSecond(byte[] source) + { + const int signatureLength = 8; + const int chunkOverheadLength = 12; + + int firstChunkLength = BinaryPrimitives.ReadInt32BigEndian(source.AsSpan(signatureLength, 4)) + chunkOverheadLength; + int secondChunkOffset = signatureLength + firstChunkLength; + int secondChunkLength = BinaryPrimitives.ReadInt32BigEndian(source.AsSpan(secondChunkOffset, 4)) + chunkOverheadLength; + byte[] reordered = new byte[source.Length]; + + source.AsSpan(0, signatureLength).CopyTo(reordered); + source.AsSpan(secondChunkOffset, secondChunkLength).CopyTo(reordered.AsSpan(signatureLength)); + source.AsSpan(signatureLength, firstChunkLength).CopyTo(reordered.AsSpan(signatureLength + secondChunkLength)); + + // Chunk CRCs cover only each chunk's type and data, so moving complete chunks + // leaves both checksums valid and isolates ordering as the tested behavior. + source.AsSpan(secondChunkOffset + secondChunkLength) + .CopyTo(reordered.AsSpan(signatureLength + secondChunkLength + firstChunkLength)); + + return reordered; + } + [Theory] [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] From 494d2dd97b7b639495f46111a3cdf4ec91cbef4f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jul 2026 20:17:31 +1000 Subject: [PATCH 216/240] Clone TIFF profiles into image metadata Update TIFF metadata creation to build ImageMetadata after IFD tag processing and copy root-frame EXIF/ICC/IPTC/XMP profiles to image scope via deep clones. This keeps image-level metadata in sync for downstream single-frame encoders while preserving independent mutability between frame and image metadata. Tests now cover preserved ICC handling and verify image-level profile presence, equality, and non-shared instances for EXIF/XMP/IPTC, including ignore-metadata behavior. --- .../Formats/Tiff/TiffDecoderMetadataCreator.cs | 18 ++++++++++++++---- .../Formats/Tiff/TiffDecoderTests.cs | 16 ++++++++++++++++ .../Formats/Tiff/TiffMetadataTests.cs | 13 +++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index ebf407f9b..f7cdd1bfc 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -22,8 +22,6 @@ internal static class TiffDecoderMetadataCreator TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); } - ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]); - if (!ignoreMetadata) { for (int i = 0; i < frames.Count; i++) @@ -43,12 +41,24 @@ internal static class TiffDecoderMetadataCreator } } - return imageMetaData; + // IPTC and XMP are materialized from IFD tags above, so image-level metadata + // must be created after all frame profiles are available. + return Create(byteOrder, isBigTiff, frames[0]); } private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata) { - ImageMetadata imageMetaData = new(); + // TIFF stores metadata per IFD, while ImageMetadata is the source used by single-frame encoders. + // Mirror the root IFD profiles at image scope so cross-format encoding preserves them. Each profile + // is cloned because image-level and frame-level metadata are independently mutable public APIs. + ImageMetadata imageMetaData = new() + { + ExifProfile = rootFrameMetadata.ExifProfile?.DeepClone(), + IccProfile = rootFrameMetadata.IccProfile?.DeepClone(), + IptcProfile = rootFrameMetadata.IptcProfile?.DeepClone(), + XmpProfile = rootFrameMetadata.XmpProfile?.DeepClone() + }; + SetResolution(imageMetaData, rootFrameMetadata.ExifProfile); TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index de893aca6..ec6113be3 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -370,6 +370,22 @@ public class TiffDecoderTests : TiffDecoderBaseTester Assert.Null(image.Metadata.IccProfile); } + [Theory] + [WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsPreserve_PreservesIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; + using Image image = provider.GetImage(TiffDecoder.Instance, options); + + Assert.NotNull(image.Metadata.IccProfile); + Assert.NotSame(image.Frames.RootFrame.Metadata.IccProfile, image.Metadata.IccProfile); + Assert.Equal( + image.Frames.RootFrame.Metadata.IccProfile.ToByteArray(), + image.Metadata.IccProfile.ToByteArray()); + } + [Theory] [WithFile(Issues2454_A, PixelTypes.Rgba32)] [WithFile(Issues2454_B, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 94ed54326..9f0f0e991 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -150,13 +150,22 @@ public class TiffMetadataTests Assert.NotNull(meta); if (ignoreMetadata) { + Assert.Null(image.Metadata.XmpProfile); + Assert.Null(image.Metadata.ExifProfile); Assert.Null(rootFrameMetaData.XmpProfile); Assert.Null(rootFrameMetaData.ExifProfile); } else { + Assert.NotNull(image.Metadata.XmpProfile); + Assert.NotNull(image.Metadata.ExifProfile); Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); + + Assert.NotSame(rootFrameMetaData.XmpProfile, image.Metadata.XmpProfile); + Assert.NotSame(rootFrameMetaData.ExifProfile, image.Metadata.ExifProfile); + Assert.Equal(rootFrameMetaData.XmpProfile.Data, image.Metadata.XmpProfile.Data); + Assert.Equal(rootFrameMetaData.ExifProfile.ToByteArray(), image.Metadata.ExifProfile.ToByteArray()); Assert.Equal(2596, rootFrameMetaData.XmpProfile.Data.Length); // padding bytes are trimmed Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count); } @@ -171,6 +180,10 @@ public class TiffMetadataTests IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; Assert.NotNull(iptcProfile); + Assert.NotNull(image.Metadata.IptcProfile); + Assert.NotSame(iptcProfile, image.Metadata.IptcProfile); + Assert.Equal(iptcProfile.Data, image.Metadata.IptcProfile.Data); + IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); Assert.NotNull(byline); Assert.Equal("Studio Mantyniemi", byline.Value); From e2b43407cc2ce5fe4bab23200f1e08aed1ff0113 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jul 2026 20:37:58 +1000 Subject: [PATCH 217/240] Handle ulong values for EXIF Long writes Fixes EXIF serialization when `ExifLong8Array` downgrades to `ExifDataType.Long` but still stores values as `ulong`. `ExifWriter.WriteValue` now handles `ulong` inputs in the `Long` branch by writing 32-bit values, and a BigTIFF metadata test verifies mixed in-range values are written correctly as two little-endian `uint`s. --- .../Metadata/Profiles/Exif/ExifWriter.cs | 7 +++++++ .../Formats/Tiff/BigTiffMetadataTests.cs | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 732e3eab2..659df01a6 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -403,6 +403,13 @@ internal sealed class ExifWriter return WriteUInt32((uint)longNumber, destination, offset); } + // ExifLong8Array retains ulong storage but reports Long when every value fits + // in 32 bits, allowing BigTIFF offsets to be serialized by classic EXIF writers. + if (value is ulong long8Value) + { + return WriteUInt32((uint)long8Value, destination, offset); + } + return WriteUInt32((uint)value, destination, offset); case ExifDataType.Long8: return WriteUInt64((ulong)value, destination, offset); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index d19f27807..6aebf7b9c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -88,6 +88,21 @@ public class BigTiffMetadataTests Assert.Equal(ExifDataType.Long8, long8.DataType); } + [Fact] + public void ExifLong8Array_CanWriteValuesAsLong() + { + ExifLong8Array long8 = new(ExifTagValue.StripOffsets); + Assert.True(long8.TrySetValue(new long[] { 1, uint.MaxValue })); + Assert.Equal(ExifDataType.Long, long8.DataType); + + byte[] buffer = new byte[8]; + int written = ExifWriter.WriteValue(long8, buffer, 0); + + Assert.Equal(buffer.Length, written); + Assert.Equal(1U, BinaryPrimitives.ReadUInt32LittleEndian(buffer)); + Assert.Equal(uint.MaxValue, BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(4))); + } + [Fact] public void ExifSignedLong8Array() { From b142dbacffb314ef751cd78ffb3eb833d021c9da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Jul 2026 23:17:14 +1000 Subject: [PATCH 218/240] Add AOT compiler seeds for ICO, CUR, QOI formats - Add ICO, CUR, and QOI encoder/decoder seeding in AotCompilerTools - Introduce SeedPixelOperations() to explicitly seed all built-in pixel format PixelOperations specializations for Mono WASM AOT - Add [DynamicDependency] on Configuration constructors to keep the seed graph visible to .NET trimmers - Add AI coding guideline files (AGENTS.md, CLAUDE.md, GEMINI.md, copilot-instructions.md) - Update .gitattributes to treat .a, .dylib, and .so as binary --- .gitattributes | 3 + .github/copilot-instructions.md | 3 + AGENTS.md | 41 ++++++ CLAUDE.md | 3 + GEMINI.md | 3 + src/ImageSharp/Advanced/AotCompilerTools.cs | 137 ++++++++++++++++++++ src/ImageSharp/Configuration.cs | 7 + 7 files changed, 197 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md diff --git a/.gitattributes b/.gitattributes index 7de2c1597..031420e53 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,12 +84,15 @@ # treat as binary ############################################################################### *.basis binary +*.a binary *.dll binary +*.dylib binary *.exe binary *.pdf binary *.ppt binary *.pptx binary *.pvr binary +*.so binary *.snk binary *.xls binary *.xlsx binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..5f9f69435 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,3 @@ +# GitHub Copilot Instructions + +Read and follow [AGENTS.md](../AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Prefer existing local patterns and repository configuration whenever generated code or suggestions are accepted. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..814e3ce26 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Six Labors AI Coding Guidelines + +These instructions apply to the entire repository. More-specific `AGENTS.md` files may add to or override them for their directory tree. + +## Working Practices + +- Inspect the relevant implementation, tests, benchmarks, project files, and nearby code before proposing or making changes. Do not infer current behavior when the source is available. +- Make the smallest complete change that solves the requested problem. Avoid unrelated cleanup, speculative abstractions, and formatting churn. +- Match established architecture, naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative. +- Preserve public API and observable behavior unless the task explicitly requires a change. Public API documentation must describe observable behavior, not implementation details. +- Do not use reflection against built assemblies, ad hoc assembly loading, or temporary probe projects unless explicitly requested. +- Build .NET projects in Release configuration unless explicitly instructed otherwise. + +## Performance + +- Treat throughput, latency, memory use, and binary size as design constraints, especially in pixel-processing, drawing, parsing, encoding, and other hot paths. +- Avoid unnecessary allocations, copies, boxing, closures, interface dispatch, repeated enumeration, and extra passes over data. +- Reuse the repository's existing memory ownership, pooling, span, vectorization, and parallelization patterns. Do not introduce a new mechanism when an established one fits. +- Keep hot loops simple and bounds-check-friendly. Hoist invariant work, preserve locality, and use the narrowest suitable data types without sacrificing correctness. +- Do not trade correctness or maintainability for assumed speed. Support non-obvious optimizations with measurements or clear evidence, and add or update benchmarks when performance is the purpose of the change. +- Consider all supported target frameworks and runtime capabilities. Do not regress fallback paths while optimizing newer runtimes. + +## C# Conventions + +- Follow the existing code around the change; local patterns take precedence over generic preferences. +- Do not use `record` or `record struct` types. +- Prefer established invariants over redundant guards. Validate at real external boundaries and do not add defensive checks for internally controlled states. +- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or meaningful complexity reduction. +- Add vertical whitespace after multi-line statements and declarations and between distinct logical stages. Never add trailing whitespace. +- Document every method, constructor, and property, regardless of whether it is public, internal, protected, or private. Keep public API documentation limited to observable behavior; use private and internal documentation to capture the contract and intent needed to maintain the code. +- Add inline comments throughout complex code. Explain algorithms, formulas, invariants, ownership, compatibility behavior, and performance tradeoffs at the operations and decisions they govern. Explain why the code is shaped that way rather than narrating the syntax. +- Document SIMD code especially thoroughly. Explain the vector layout, lane meaning, widening or narrowing, masks, shuffles, constants, alignment or remainder handling, supported instruction paths, scalar equivalence, and the reason each non-obvious operation is correct. +- Write algorithm and SIMD comments for a maintainer who is unfamiliar with the implementation. The reader should not need to reconstruct intent from external documentation, issue history, or benchmark results. + +## Verification + +- Add or update focused tests when behavior changes, following the test framework and conventions already used by the project. +- Never hack, weaken, skip, conditionally bypass, or otherwise manipulate a test to make it pass. Fix the production defect or the genuine test defect while preserving the test's intended coverage and sensitivity. +- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts to resolve a test failure. Treat a mismatch as evidence to investigate and correct the implementation. +- Run the narrowest relevant formatting, test, and Release build commands, then expand verification in proportion to the risk and scope of the change. +- Report what changed, the verification performed, and any remaining risks or unverified assumptions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..5f08a449a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code Instructions + +Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `CLAUDE.md` found below the files being changed. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000..b621d2652 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,3 @@ +# Gemini CLI Instructions + +Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `GEMINI.md` found below the files being changed. diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 6d37fd5ce..63cc912eb 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -7,8 +7,10 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Ico; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -128,6 +130,67 @@ internal static class AotCompilerTools throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime."); } + /// + /// Seeds the modern .NET AOT compiler with bulk pixel operations for every built-in pixel format. + /// + /// + /// This method is used for AOT code generation only. Do not call it at runtime. + /// + [Preserve] + public static void SeedPixelOperations() + { + try + { + // Keep this inventory finite and explicit. ImageSharp cannot precompile consumer-defined pixel types, while + // these closed calls make every built-in specialization visible without retaining the much larger legacy seed graph. + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + AotCompilePixelOperations(); + } + catch + { + // The calls only need to exist in IL; this method must never contribute a runtime execution path. + } + + throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime."); + } + /// /// Seeds the compiler using the given pixel format. /// @@ -138,6 +201,7 @@ internal static class AotCompilerTools { // This is we actually call all the individual methods you need to seed. AotCompileImage(); + AotCompilePixelOperations(); AotCompileImageProcessingContextFactory(); AotCompileImageEncoderInternals(); AotCompileImageDecoderInternals(); @@ -158,6 +222,69 @@ internal static class AotCompilerTools // TODO: Do the discovery work to figure out what works and what doesn't. } + /// + /// Seeds every bulk operation exposed by . + /// + /// The pixel format. + [Preserve] + private static void AotCompilePixelOperations() + where TPixel : unmanaged, IPixel + { + // These default arguments are never consumed. Direct calls are required so the IL contains the exact closed + // MethodSpecs that Mono WASM AOT can otherwise miss when following static-abstract pixel dispatch indirectly. + _ = PixelOperations.Instance; + PixelOperations operations = default; + + _ = operations.GetPixelTypeInfo(); + _ = operations.GetPixelBlender(default(GraphicsOptions)); + _ = operations.GetPixelBlender(default, default); + operations.FromVector4Destructive(default, default, default); + operations.FromVector4Destructive(default, default, default, default); + operations.ToVector4(default, default, default); + operations.ToVector4(default, default, default, default); + operations.PackFromRgbPlanes(default, default, default, default); + operations.UnpackIntoRgbPlanes(default, default, default, default); + + operations.FromArgb32Bytes(default, default, default, default); + operations.ToArgb32Bytes(default, default, default, default); + + operations.FromAbgr32Bytes(default, default, default, default); + operations.ToAbgr32Bytes(default, default, default, default); + + operations.FromBgr24Bytes(default, default, default, default); + operations.ToBgr24Bytes(default, default, default, default); + + operations.FromBgra32Bytes(default, default, default, default); + operations.ToBgra32Bytes(default, default, default, default); + + operations.FromL8Bytes(default, default, default, default); + operations.ToL8Bytes(default, default, default, default); + + operations.FromL16Bytes(default, default, default, default); + operations.ToL16Bytes(default, default, default, default); + + operations.FromLa16Bytes(default, default, default, default); + operations.ToLa16Bytes(default, default, default, default); + + operations.FromLa32Bytes(default, default, default, default); + operations.ToLa32Bytes(default, default, default, default); + + operations.FromRgb24Bytes(default, default, default, default); + operations.ToRgb24Bytes(default, default, default, default); + + operations.FromRgba32Bytes(default, default, default, default); + operations.ToRgba32Bytes(default, default, default, default); + + operations.FromRgb48Bytes(default, default, default, default); + operations.ToRgb48Bytes(default, default, default, default); + + operations.FromRgba64Bytes(default, default, default, default); + operations.ToRgba64Bytes(default, default, default, default); + + operations.FromBgra5551Bytes(default, default, default, default); + operations.ToBgra5551Bytes(default, default, default, default); + } + /// /// This method pre-seeds the for a given pixel format in the AoT compiler. /// @@ -229,8 +356,10 @@ internal static class AotCompilerTools where TPixel : unmanaged, IPixel { default(BmpEncoderCore).Encode(default, default, default); + default(CurEncoderCore).Encode(default, default, default); default(ExrEncoderCore).Encode(default, default, default); default(GifEncoderCore).Encode(default, default, default); + default(IcoEncoderCore).Encode(default, default, default); default(JpegEncoderCore).Encode(default, default, default); default(PbmEncoderCore).Encode(default, default, default); default(PngEncoderCore).Encode(default, default, default); @@ -249,8 +378,10 @@ internal static class AotCompilerTools where TPixel : unmanaged, IPixel { default(BmpDecoderCore).Decode(default, default, default); + default(CurDecoderCore).Decode(default, default, default); default(ExrDecoderCore).Decode(default, default, default); default(GifDecoderCore).Decode(default, default, default); + default(IcoDecoderCore).Decode(default, default, default); default(JpegDecoderCore).Decode(default, default, default); default(PbmDecoderCore).Decode(default, default, default); default(PngDecoderCore).Decode(default, default, default); @@ -270,11 +401,14 @@ internal static class AotCompilerTools { AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); } @@ -289,11 +423,14 @@ internal static class AotCompilerTools { AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c09d78221..1e5433b9e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; @@ -38,6 +40,9 @@ public sealed class Configuration /// /// Initializes a new instance of the class. /// + // Every image operation requires a Configuration. Attaching the dependency to its constructors keeps the compile-only + // seed graph visible to modern .NET trimmers without executing that graph or adding module-initialization work. + [DynamicDependency(nameof(AotCompilerTools.SeedPixelOperations), typeof(AotCompilerTools))] public Configuration() { } @@ -46,6 +51,8 @@ public sealed class Configuration /// Initializes a new instance of the class. /// /// A collection of configuration modules to register. + // The default Configuration is created through this overload, while callers may use either constructor. + [DynamicDependency(nameof(AotCompilerTools.SeedPixelOperations), typeof(AotCompilerTools))] public Configuration(params IImageFormatConfigurationModule[] configurationModules) { if (configurationModules != null) From 395c8bdb655fecdf7f835ede9798de4228380313 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Jul 2026 11:36:31 +1000 Subject: [PATCH 219/240] Update AotCompilerTools.cs --- src/ImageSharp/Advanced/AotCompilerTools.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 63cc912eb..2d893e406 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -223,7 +223,7 @@ internal static class AotCompilerTools } /// - /// Seeds every bulk operation exposed by . + /// Seeds the selected methods required by Mono WASM AOT. /// /// The pixel format. [Preserve] @@ -232,8 +232,7 @@ internal static class AotCompilerTools { // These default arguments are never consumed. Direct calls are required so the IL contains the exact closed // MethodSpecs that Mono WASM AOT can otherwise miss when following static-abstract pixel dispatch indirectly. - _ = PixelOperations.Instance; - PixelOperations operations = default; + PixelOperations operations = PixelOperations.Instance; _ = operations.GetPixelTypeInfo(); _ = operations.GetPixelBlender(default(GraphicsOptions)); From ebe5cc64da3c95cad453fa782cddb8c373dbfe5a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Jul 2026 23:40:13 +1000 Subject: [PATCH 220/240] Add TensorPrimitives compatibility implementation --- shared-infrastructure | 2 +- .../ColorProfileConverterExtensionsIcc.cs | 73 +- src/ImageSharp/Common/Helpers/Numerics.cs | 186 +- .../Common/Helpers/TensorPrimitives.cs | 1566 +++++++++++++++++ .../Components/Encoder/ComponentProcessor.cs | 95 +- .../Formats/Png/Filters/UpFilter.cs | 126 +- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 27 +- .../Formats/Webp/Lossless/Vp8LHistogram.cs | 47 +- .../General/BasicMath/AddSpan.cs | 65 + .../General/BasicMath/NormalizeSpan.cs | 61 + .../TensorPrimitivesAssemblyComparison.cs | 813 +++++++++ .../ImageSharp.Benchmarks.csproj | 1 + .../Common/TensorPrimitivesTests.cs | 356 ++++ 13 files changed, 2886 insertions(+), 532 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives.cs create mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs create mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs create mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs create mode 100644 tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs diff --git a/shared-infrastructure b/shared-infrastructure index 7ac570345..74b7f32b8 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 7ac5703452348d9295db31fc0912c2bd9e419dc9 +Subproject commit 74b7f32b8e41fdf8fe2f3eda54fd5a82ebbedfbc diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index fd99fb446..78f88932f 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -5,9 +5,11 @@ using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.ColorProfiles.Icc; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -658,38 +660,8 @@ internal static class ColorProfileConverterExtensionsIcc private static void ClipNegative(Span source) { - if (Vector.IsHardwareAccelerated && Vector.IsSupported && Vector.Count >= source.Length * 4) - { - // SIMD loop - int i = 0; - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - v = Vector.Max(v, Vector.Zero); - - // Write the vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref source[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - ref Vector4 s = ref source[i]; - s = Vector4.Max(s, Vector4.Zero); - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - ref Vector4 s = ref source[i]; - s = Vector4.Max(s, Vector4.Zero); - } - } + Span values = MemoryMarshal.Cast(source); + TensorPrimitives_.Max(values, 0F, values); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -708,39 +680,10 @@ internal static class ColorProfileConverterExtensionsIcc private static void LabToLab(Span source, Span destination, [ConstantExpected] float scale) { - if (Vector.IsHardwareAccelerated && Vector.IsSupported) - { - Vector vScale = new(scale); - int i = 0; - - // SIMD loop - int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch - for (; i <= source.Length - simdBatchSize; i += simdBatchSize) - { - // Load the vector from source span - Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i])); - - // Scale the vector - v *= vScale; - - // Write the scaled vector to the destination span - Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[i]), v); - } - - // Scalar fallback for remaining elements - for (; i < source.Length; i++) - { - destination[i] = source[i] * scale; - } - } - else - { - // Scalar fallback if SIMD is not supported - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i] * scale; - } - } + TensorPrimitives_.Multiply( + MemoryMarshal.Cast(source), + scale, + MemoryMarshal.Cast(destination)); } private class ConversionParams diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 5ffed8eb7..8980d2b53 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -329,22 +329,7 @@ internal static class Numerics /// The maximum inclusive value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(Span span, byte min, byte max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref byte remainderStart = ref MemoryMarshal.GetReference(remainder); - ref byte remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } + => TensorPrimitives_.Clamp(span, min, max, span); /// /// Clamps the span values to the inclusive range of min and max. @@ -354,22 +339,7 @@ internal static class Numerics /// The maximum inclusive value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(Span span, uint min, uint max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref uint remainderStart = ref MemoryMarshal.GetReference(remainder); - ref uint remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } + => TensorPrimitives_.Clamp(span, min, max, span); /// /// Clamps the span values to the inclusive range of min and max. @@ -379,22 +349,7 @@ internal static class Numerics /// The maximum inclusive value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(Span span, int min, int max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref int remainderStart = ref MemoryMarshal.GetReference(remainder); - ref int remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } + => TensorPrimitives_.Clamp(span, min, max, span); /// /// Clamps the span values to the inclusive range of min and max. @@ -404,22 +359,7 @@ internal static class Numerics /// The maximum inclusive value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(Span span, float min, float max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref float remainderStart = ref MemoryMarshal.GetReference(remainder); - ref float remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } + => TensorPrimitives_.Clamp(span, min, max, span); /// /// Clamps the span values to the inclusive range of min and max. @@ -429,87 +369,7 @@ internal static class Numerics /// The maximum inclusive value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(Span span, double min, double max) - { - Span remainder = span[ClampReduce(span, min, max)..]; - - if (remainder.Length > 0) - { - ref double remainderStart = ref MemoryMarshal.GetReference(remainder); - ref double remainderEnd = ref Unsafe.Add(ref remainderStart, (uint)remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ClampReduce(Span span, T min, T max) - where T : unmanaged - { - if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) - { - int remainder = ModuloP2(span.Length, Vector.Count); - int adjustedCount = span.Length - remainder; - - if (adjustedCount > 0) - { - ClampImpl(span[..adjustedCount], min, max); - } - - return adjustedCount; - } - - return 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ClampImpl(Span span, T min, T max) - where T : unmanaged - { - ref T sRef = ref MemoryMarshal.GetReference(span); - Vector vmin = new(min); - Vector vmax = new(max); - - nint n = (nint)(uint)span.Length / Vector.Count; - nint m = Modulo4(n); - nint u = n - m; - - ref Vector vs0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - ref Vector vs1 = ref Unsafe.Add(ref vs0, 1); - ref Vector vs2 = ref Unsafe.Add(ref vs0, 2); - ref Vector vs3 = ref Unsafe.Add(ref vs0, 3); - ref Vector vsEnd = ref Unsafe.Add(ref vs0, u); - - while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) - { - vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - vs1 = Vector.Min(Vector.Max(vmin, vs1), vmax); - vs2 = Vector.Min(Vector.Max(vmin, vs2), vmax); - vs3 = Vector.Min(Vector.Max(vmin, vs3), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 4); - vs1 = ref Unsafe.Add(ref vs1, 4); - vs2 = ref Unsafe.Add(ref vs2, 4); - vs3 = ref Unsafe.Add(ref vs3, 4); - } - - if (m > 0) - { - vs0 = ref vsEnd; - vsEnd = ref Unsafe.Add(ref vsEnd, m); - - while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) - { - vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 1); - } - } - } + => TensorPrimitives_.Clamp(span, min, max, span); /// /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. @@ -1210,39 +1070,5 @@ internal static class Numerics /// The sum of the values in . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Normalize(Span span, float sum) - { - if (Vector256.IsHardwareAccelerated) - { - ref float startRef = ref MemoryMarshal.GetReference(span); - ref float endRef = ref Unsafe.Add(ref startRef, span.Length & ~7); - Vector256 sum256 = Vector256.Create(sum); - - while (Unsafe.IsAddressLessThan(ref startRef, ref endRef)) - { - Unsafe.As>(ref startRef) /= sum256; - startRef = ref Unsafe.Add(ref startRef, (nuint)8); - } - - if ((span.Length & 7) >= 4) - { - Unsafe.As>(ref startRef) /= sum256.GetLower(); - startRef = ref Unsafe.Add(ref startRef, (nuint)4); - } - - endRef = ref Unsafe.Add(ref startRef, span.Length & 3); - - while (Unsafe.IsAddressLessThan(ref startRef, ref endRef)) - { - startRef /= sum; - startRef = ref Unsafe.Add(ref startRef, (nuint)1); - } - } - else - { - for (int i = 0; i < span.Length; i++) - { - span[i] /= sum; - } - } - } + => TensorPrimitives_.Divide(span, sum, span); } diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs new file mode 100644 index 000000000..7f97776ab --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs @@ -0,0 +1,1566 @@ +// 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.Common.Helpers; + +/// +/// Provides compatibility implementations for tensor operations that are not available on every target framework. +/// +/// +/// The API shape follows System.Numerics.Tensors.TensorPrimitives so call sites can move to the runtime +/// implementation when ImageSharp no longer supports target frameworks that predate it. +/// +#pragma warning disable SA1649 // File name should match first type name +internal static class TensorPrimitives_ +#pragma warning restore SA1649 // File name should match first type name +{ + /// + /// Defines an element-wise binary operation. + /// + /// The element type. + private interface IBinaryOperator + { + /// + /// Gets a value indicating whether the operation supports vector execution. + /// + public static abstract bool Vectorizable { get; } + + /// + /// Applies the operation to scalar values. + /// + /// The first value. + /// The second value. + /// The operation result. + public static abstract T Invoke(T x, T y); + + /// + /// Applies the operation to 128-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The operation result. + public static abstract Vector128 Invoke(Vector128 x, Vector128 y); + + /// + /// Applies the operation to 256-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The operation result. + public static abstract Vector256 Invoke(Vector256 x, Vector256 y); + + /// + /// Applies the operation to 512-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The operation result. + public static abstract Vector512 Invoke(Vector512 x, Vector512 y); + } + + /// + /// Defines an element-wise ternary operation. + /// + /// The element type. + private interface ITernaryOperator + { + /// + /// Gets a value indicating whether the operation supports vector execution. + /// + public static abstract bool Vectorizable { get; } + + /// + /// Applies the operation to scalar values. + /// + /// The first value. + /// The second value. + /// The third value. + /// The operation result. + public static abstract T Invoke(T x, T y, T z); + + /// + /// Applies the operation to 128-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The third vector. + /// The operation result. + public static abstract Vector128 Invoke(Vector128 x, Vector128 y, Vector128 z); + + /// + /// Applies the operation to 256-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The third vector. + /// The operation result. + public static abstract Vector256 Invoke(Vector256 x, Vector256 y, Vector256 z); + + /// + /// Applies the operation to 512-bit vectors. + /// + /// The first vector. + /// The second vector. + /// The third vector. + /// The operation result. + public static abstract Vector512 Invoke(Vector512 x, Vector512 y, Vector512 z); + } + + /// + /// Computes the element-wise result of clamping to the inclusive range specified + /// by and . + /// + /// The element type. + /// The values to clamp. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The destination for the clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(ReadOnlySpan x, T min, T max, Span destination) + where T : INumber + => InvokeSpanScalarScalarIntoSpan>(x, min, max, destination); + + /// + /// Computes the element-wise sum of the values in and . + /// + /// The element type. + /// The first addends. + /// The second addends. + /// The destination for the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(ReadOnlySpan x, ReadOnlySpan y, Span destination) + where T : IAdditionOperators, IAdditiveIdentity + => InvokeSpanSpanIntoSpan>(x, y, destination); + + /// + /// Computes the element-wise result of dividing the values in by . + /// + /// The element type. + /// The dividend values. + /// The divisor. + /// The destination for the quotient values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Divide(ReadOnlySpan x, T y, Span destination) + where T : IDivisionOperators + => InvokeSpanScalarIntoSpanForDivision>(x, y, destination); + + /// + /// Computes the element-wise maximum of the values in and . + /// + /// The element type. + /// The values to compare. + /// The value to compare with each element. + /// The destination for the maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Max(ReadOnlySpan x, T y, Span destination) + where T : INumber + => InvokeSpanScalarIntoSpan>(x, y, destination); + + /// + /// Computes the element-wise product of the values in and . + /// + /// The element type. + /// The multiplicands. + /// The multiplier. + /// The destination for the products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Multiply(ReadOnlySpan x, T y, Span destination) + where T : IMultiplyOperators, IMultiplicativeIdentity + => InvokeSpanScalarIntoSpan>(x, y, destination); + + /// + /// Performs an element-wise binary operation between two spans. + /// + /// The element type. + /// The operation to apply. + /// The first input values. + /// The second input values. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeSpanSpanIntoSpan( + ReadOnlySpan x, + ReadOnlySpan y, + Span destination) + where TOperator : struct, IBinaryOperator + { + ref T xRef = ref MemoryMarshal.GetReference(x); + ref T yRef = ref MemoryMarshal.GetReference(y); + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)x.Length; + + // AVX-512 setup only pays off for larger multi-byte inputs. Byte addition remains on AVX2 because direct + // PNG/WebP measurements show that its higher lane count does not recover the wider dispatch cost. + // Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct. + if (TOperator.Vectorizable + && Vector512.IsHardwareAccelerated + && Vector512.IsSupported + && Unsafe.SizeOf() > 1 + && length >= 512) + { + InvokeVectorized512(ref xRef, ref yRef, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256.IsSupported && length >= (uint)Vector256.Count) + { + InvokeVectorized256(ref xRef, ref yRef, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128.IsSupported && length >= (uint)Vector128.Count) + { + InvokeVectorized128(ref xRef, ref yRef, ref destinationRef, length); + return; + } + + for (nuint i = 0; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), Unsafe.Add(ref yRef, i)); + } + } + + /// + /// Performs an element-wise binary operation between a span and a scalar. + /// + /// The element type. + /// The operation to apply. + /// The input values. + /// The scalar input. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeSpanScalarIntoSpan( + ReadOnlySpan x, + T y, + Span destination) + where TOperator : struct, IBinaryOperator + { + ref T xRef = ref MemoryMarshal.GetReference(x); + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)x.Length; + + // The runtime-style unrolled 512-bit pipeline wins on large inputs, but its setup cost regresses the + // shorter JPEG and ICC buffers. Measurements put the crossover safely below 512 elements. + if (TOperator.Vectorizable + && Vector512.IsHardwareAccelerated + && Vector512.IsSupported + && length >= 512) + { + InvokeVectorized512(ref xRef, y, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256.IsSupported && length >= (uint)Vector256.Count) + { + InvokeVectorized256(ref xRef, y, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128.IsSupported && length >= (uint)Vector128.Count) + { + InvokeVectorized128(ref xRef, y, ref destinationRef, length); + return; + } + + for (nuint i = 0; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); + } + } + + /// + /// Performs element-wise division using thresholds measured for ImageSharp normalization workloads. + /// + /// The element type. + /// The division operation to apply. + /// The input values. + /// The scalar divisor. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeSpanScalarIntoSpanForDivision( + ReadOnlySpan x, + T y, + Span destination) + where TOperator : struct, IBinaryOperator + { + ref T xRef = ref MemoryMarshal.GetReference(x); + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)x.Length; + + // AVX-512 only wins once there is enough work to amortize its wider dispatch and division latency. + // Eight vectors is also the runtime pipeline's unrolled-loop boundary, while shorter inputs retain + // the lower setup cost of 256-bit vectors. + if (TOperator.Vectorizable + && Vector512.IsHardwareAccelerated + && Vector512.IsSupported + && length >= (uint)(Vector512.Count * 8)) + { + InvokeVectorized512(ref xRef, y, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256.IsSupported && length >= (uint)Vector256.Count) + { + InvokeVectorized256(ref xRef, y, ref destinationRef, length); + return; + } + + // Four values fill one 128-bit float vector. Processing exactly one packed prefix before the scalar + // remainder avoids the overlapping second vector that regresses the common seven-element normalization. + if (TOperator.Vectorizable + && Vector128.IsHardwareAccelerated + && Vector128.IsSupported + && length >= (uint)Vector128.Count) + { + nuint vectorCount = (uint)Vector128.Count; + Vector128 yVector = Vector128.Create(y); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef), yVector).StoreUnsafe(ref destinationRef); + + for (nuint i = vectorCount; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); + } + + return; + } + + for (nuint i = 0; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); + } + } + + /// + /// Performs an element-wise ternary operation between a span and two scalars. + /// + /// The element type. + /// The operation to apply. + /// The input values. + /// The first scalar input. + /// The second scalar input. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeSpanScalarScalarIntoSpan( + ReadOnlySpan x, + T y, + T z, + Span destination) + where TOperator : struct, ITernaryOperator + { + ref T xRef = ref MemoryMarshal.GetReference(x); + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)x.Length; + + // This dispatch mirrors the runtime pipeline: large inputs use the widest available registers while + // short inputs fall through to a width that fits, keeping the operator contract identical at every length. + if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported && length >= (uint)Vector512.Count) + { + InvokeVectorized512(ref xRef, y, z, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256.IsSupported && length >= (uint)Vector256.Count) + { + InvokeVectorized256(ref xRef, y, z, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128.IsSupported && length >= (uint)Vector128.Count) + { + InvokeVectorized128(ref xRef, y, z, ref destinationRef, length); + return; + } + + for (nuint i = 0; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y, z); + } + } + + /// + /// Applies a binary operation between two spans with 128-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first element of the first input. + /// The first element of the second input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized128( + ref T xRef, + ref T yRef, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector128.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + + // When a tail exists, both final inputs are loaded before any stores. This permits either source to also + // be the destination when the tail starts inside the range written by the preceding full vector. + Vector128 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector128.LoadUnsafe(ref xRef, length - vectorCount), + Vector128.LoadUnsafe(ref yRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), Vector128.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a binary operation between two spans with 256-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first element of the first input. + /// The first element of the second input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized256( + ref T xRef, + ref T yRef, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector256.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector256 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector256.LoadUnsafe(ref xRef, length - vectorCount), + Vector256.LoadUnsafe(ref yRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), Vector256.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a binary operation between two spans with 512-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first element of the first input. + /// The first element of the second input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized512( + ref T xRef, + ref T yRef, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector512.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector512 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector512.LoadUnsafe(ref xRef, length - vectorCount), + Vector512.LoadUnsafe(ref yRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), Vector512.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a binary operation between a span and a scalar with 128-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized128( + ref T xRef, + T y, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector128.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector128 yVector = Vector128.Create(y); + + // When a tail exists, preloading its final vector is required for in-place operation because it must + // observe the original values before an earlier overlapping store writes them. + Vector128 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector128.LoadUnsafe(ref xRef, length - vectorCount), + yVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a binary operation with 256-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized256( + ref T xRef, + T y, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector256.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector256 yVector = Vector256.Create(y); + Vector256 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector256.LoadUnsafe(ref xRef, length - vectorCount), + yVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a binary operation with 512-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized512( + ref T xRef, + T y, + ref T destinationRef, + nuint length) + where TOperator : struct, IBinaryOperator + { + nuint vectorCount = (uint)Vector512.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector512 yVector = Vector512.Create(y); + Vector512 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector512.LoadUnsafe(ref xRef, length - vectorCount), + yVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MaxSingle(Vector128 x, Vector128 y) + { + // The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a + // first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order. + Vector128 result = Vector128.Max(x, y); + result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); + + return Vector128.ConditionalSelect( + Vector128.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 MaxSingle(Vector256 x, Vector256 y) + { + Vector256 result = Vector256.Max(x, y); + result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); + + return Vector256.ConditionalSelect( + Vector256.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 MaxSingle(Vector512 x, Vector512 y) + { + Vector512 result = Vector512.Max(x, y); + result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); + + return Vector512.ConditionalSelect( + Vector512.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MaxDouble(Vector128 x, Vector128 y) + { + Vector128 result = Vector128.Max(x, y); + result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); + + return Vector128.ConditionalSelect( + Vector128.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 MaxDouble(Vector256 x, Vector256 y) + { + Vector256 result = Vector256.Max(x, y); + result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); + + return Vector256.ConditionalSelect( + Vector256.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 MaxDouble(Vector512 x, Vector512 y) + { + Vector512 result = Vector512.Max(x, y); + result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); + + return Vector512.ConditionalSelect( + Vector512.Equals(x, y), + x & y, + result); + } + + /// + /// Applies a ternary operation with 128-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first scalar input. + /// The second scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized128( + ref T xRef, + T y, + T z, + ref T destinationRef, + nuint length) + where TOperator : struct, ITernaryOperator + { + nuint vectorCount = (uint)Vector128.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector128 yVector = Vector128.Create(y); + Vector128 zVector = Vector128.Create(z); + Vector128 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector128.LoadUnsafe(ref xRef, length - vectorCount), + yVector, + zVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a ternary operation with 256-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first scalar input. + /// The second scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized256( + ref T xRef, + T y, + T z, + ref T destinationRef, + nuint length) + where TOperator : struct, ITernaryOperator + { + nuint vectorCount = (uint)Vector256.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector256 yVector = Vector256.Create(y); + Vector256 zVector = Vector256.Create(z); + Vector256 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector256.LoadUnsafe(ref xRef, length - vectorCount), + yVector, + zVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a ternary operation with 512-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first scalar input. + /// The second scalar input. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeVectorized512( + ref T xRef, + T y, + T z, + ref T destinationRef, + nuint length) + where TOperator : struct, ITernaryOperator + { + nuint vectorCount = (uint)Vector512.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector512 yVector = Vector512.Create(y); + Vector512 zVector = Vector512.Create(z); + Vector512 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke( + Vector512.LoadUnsafe(ref xRef, length - vectorCount), + yVector, + zVector); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampSingle( + Vector128 value, + Vector128 min, + Vector128 max) + { + // Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the + // first operand and select negative zero when equal values have different signs. + Vector128 maximum = Vector128.ConditionalSelect( + Vector128.LessThan(min, value) + | ~Vector128.Equals(value, value) + | (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector128.ConditionalSelect( + Vector128.LessThan(maximum, max) + | ~Vector128.Equals(maximum, maximum) + | (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampSingle( + Vector256 value, + Vector256 min, + Vector256 max) + { + Vector256 maximum = Vector256.ConditionalSelect( + Vector256.LessThan(min, value) + | ~Vector256.Equals(value, value) + | (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector256.ConditionalSelect( + Vector256.LessThan(maximum, max) + | ~Vector256.Equals(maximum, maximum) + | (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampSingle( + Vector512 value, + Vector512 min, + Vector512 max) + { + Vector512 maximum = Vector512.ConditionalSelect( + Vector512.LessThan(min, value) + | ~Vector512.Equals(value, value) + | (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector512.ConditionalSelect( + Vector512.LessThan(maximum, max) + | ~Vector512.Equals(maximum, maximum) + | (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampDouble( + Vector128 value, + Vector128 min, + Vector128 max) + { + Vector128 maximum = Vector128.ConditionalSelect( + Vector128.LessThan(min, value) + | ~Vector128.Equals(value, value) + | (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector128.ConditionalSelect( + Vector128.LessThan(maximum, max) + | ~Vector128.Equals(maximum, maximum) + | (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampDouble( + Vector256 value, + Vector256 min, + Vector256 max) + { + Vector256 maximum = Vector256.ConditionalSelect( + Vector256.LessThan(min, value) + | ~Vector256.Equals(value, value) + | (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector256.ConditionalSelect( + Vector256.LessThan(maximum, max) + | ~Vector256.Equals(maximum, maximum) + | (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampDouble( + Vector512 value, + Vector512 min, + Vector512 max) + { + Vector512 maximum = Vector512.ConditionalSelect( + Vector512.LessThan(min, value) + | ~Vector512.Equals(value, value) + | (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector512.ConditionalSelect( + Vector512.LessThan(maximum, max) + | ~Vector512.Equals(maximum, maximum) + | (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Determines whether has the same vector division support as . + /// + /// The element type. + /// when is a 32-bit signed native integer type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInt32Like() + => typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint)); + + /// + /// Adds corresponding values. + /// + /// The element type. + private readonly struct AddOperator : IBinaryOperator + where T : IAdditionOperators, IAdditiveIdentity + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Adds scalar values. + /// + /// The first addend. + /// The second addend. + /// The sum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x + y; + + /// + /// Adds 128-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x + y; + + /// + /// Adds 256-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x + y; + + /// + /// Adds 512-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x + y; + } + + /// + /// Clamps values using the complete runtime tensor contract, including signed-zero correction. + /// + /// The element type. + private readonly struct ClampOperator : ITernaryOperator + where T : INumber + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Clamps a scalar value. + /// + /// The value. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The clamped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T min, T max) + => Vector128.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max); + + /// + /// Clamps a 128-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 min, Vector128 max) + { + if (typeof(T) == typeof(float)) + { + Vector128 result = ClampSingle( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref min), + Unsafe.As, Vector128>(ref max)); + + return Unsafe.As, Vector128>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector128 result = ClampDouble( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref min), + Unsafe.As, Vector128>(ref max)); + + return Unsafe.As, Vector128>(ref result); + } + + return Vector128_.Clamp(x, min, max); + } + + /// + /// Clamps a 256-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 min, Vector256 max) + { + if (typeof(T) == typeof(float)) + { + Vector256 result = ClampSingle( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref min), + Unsafe.As, Vector256>(ref max)); + + return Unsafe.As, Vector256>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector256 result = ClampDouble( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref min), + Unsafe.As, Vector256>(ref max)); + + return Unsafe.As, Vector256>(ref result); + } + + return Vector256_.Clamp(x, min, max); + } + + /// + /// Clamps a 512-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 min, Vector512 max) + { + if (typeof(T) == typeof(float)) + { + Vector512 result = ClampSingle( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref min), + Unsafe.As, Vector512>(ref max)); + + return Unsafe.As, Vector512>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector512 result = ClampDouble( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref min), + Unsafe.As, Vector512>(ref max)); + + return Unsafe.As, Vector512>(ref result); + } + + return Vector512_.Clamp(x, min, max); + } + } + + /// + /// Selects the maximum corresponding values. + /// + /// The element type. + private readonly struct MaxOperator : IBinaryOperator + where T : INumber + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Selects the maximum scalar value. + /// + /// The first value. + /// The second value. + /// The maximum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => T.Max(x, y); + + /// + /// Selects the maximum values from 128-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) + { + if (typeof(T) == typeof(float)) + { + Vector128 result = MaxSingle( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref y)); + + return Unsafe.As, Vector128>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector128 result = MaxDouble( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref y)); + + return Unsafe.As, Vector128>(ref result); + } + + return Vector128.Max(x, y); + } + + /// + /// Selects the maximum values from 256-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) + { + if (typeof(T) == typeof(float)) + { + Vector256 result = MaxSingle( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref y)); + + return Unsafe.As, Vector256>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector256 result = MaxDouble( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref y)); + + return Unsafe.As, Vector256>(ref result); + } + + return Vector256.Max(x, y); + } + + /// + /// Selects the maximum values from 512-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) + { + if (typeof(T) == typeof(float)) + { + Vector512 result = MaxSingle( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref y)); + + return Unsafe.As, Vector512>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector512 result = MaxDouble( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref y)); + + return Unsafe.As, Vector512>(ref result); + } + + return Vector512.Max(x, y); + } + } + + /// + /// Multiplies corresponding values. + /// + /// The element type. + private readonly struct MultiplyOperator : IBinaryOperator + where T : IMultiplyOperators, IMultiplicativeIdentity + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Multiplies scalar values. + /// + /// The multiplicand. + /// The multiplier. + /// The product. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x * y; + + /// + /// Multiplies 128-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x * y; + + /// + /// Multiplies 256-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x * y; + + /// + /// Multiplies 512-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x * y; + } + + /// + /// Divides values by a scalar. + /// + /// The element type. + private readonly struct DivideOperator : IBinaryOperator + where T : IDivisionOperators + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => typeof(T) == typeof(float) + || typeof(T) == typeof(double) + || (Vector256.IsHardwareAccelerated && IsInt32Like()); + + /// + /// Divides scalar values. + /// + /// The dividend. + /// The divisor. + /// The quotient. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x / y; + + /// + /// Divides 128-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x / y; + + /// + /// Divides 256-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x / y; + + /// + /// Divides 512-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x / y; + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 1b0a17704..177e9b44f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -5,8 +5,8 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; @@ -116,52 +116,7 @@ internal class ComponentProcessor : IDisposable } static void SumVertical(Span target, Span source) - { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8"); - nuint count = source.Vector256Count(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); - } - } - else if (AdvSimd.IsSupported) - { - ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector128 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8"); - nuint count = source.Vector128Count(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); - } - } - else - { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - nuint count = source.VectorCount(); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); - } - - ref float targetRef = ref MemoryMarshal.GetReference(target); - ref float sourceRef = ref MemoryMarshal.GetReference(source); - for (nuint i = count * (uint)Vector.Count; i < (uint)source.Length; i++) - { - Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); - } - } - } + => TensorPrimitives_.Add(target, source, target); static void SumHorizontal(Span target, int factor) { @@ -209,50 +164,6 @@ internal class ComponentProcessor : IDisposable } static void MultiplyToAverage(Span target, float multiplier) - { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8"); - nuint count = target.Vector256Count(); - Vector256 multiplierVector = Vector256.Create(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); - } - } - else if (AdvSimd.IsSupported) - { - ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8"); - nuint count = target.Vector128Count(); - Vector128 multiplierVector = Vector128.Create(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); - } - } - else - { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - - nuint count = target.VectorCount(); - Vector multiplierVector = new(multiplier); - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; - } - - ref float targetRef = ref MemoryMarshal.GetReference(target); - for (nuint i = count * (uint)Vector.Count; i < (uint)target.Length; i++) - { - Unsafe.Add(ref targetRef, i) *= multiplier; - } - } - } + => TensorPrimitives_.Multiply(target, multiplier, target); } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 405d89e6c..d9c8e36d6 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -5,8 +5,8 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Formats.Png.Filters; @@ -27,128 +27,8 @@ internal static class UpFilter { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - if (Avx2.IsSupported) - { - DecodeAvx2(scanline, previousScanline); - } - else if (Sse2.IsSupported) - { - DecodeSse2(scanline, previousScanline); - } - else if (AdvSimd.IsSupported) - { - DecodeArm(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline); - } - } - - private static void DecodeAvx2(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - while (rb >= Vector256.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector256 prior = Unsafe.As>(ref scanRef); - Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - - Unsafe.As>(ref scanRef) = Avx2.Add(up, prior); - - offset += (uint)Vector256.Count; - rb -= Vector256.Count; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } - - private static void DecodeSse2(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - while (rb >= Vector128.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 prior = Unsafe.As>(ref scanRef); - Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - - Unsafe.As>(ref scanRef) = Sse2.Add(up, prior); - - offset += (uint)Vector128.Count; - rb -= Vector128.Count; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } - - private static void DecodeArm(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - int rb = scanline.Length; - nuint offset = 1; - const int bytesPerBatch = 16; - while (rb >= bytesPerBatch) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 prior = Unsafe.As>(ref scanRef); - Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - - Unsafe.As>(ref scanRef) = AdvSimd.Add(prior, up); - - offset += bytesPerBatch; - rb -= bytesPerBatch; - } - - // Handle left over. - for (nuint i = offset; i < (uint)scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - for (nuint x = 1; x < (uint)scanline.Length; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + above); - } + // The leading filter byte is metadata; every remaining byte is the modulo-256 sum of Raw(x) and Prior(x). + TensorPrimitives_.Add(scanline[1..], previousScanline[1..], scanline[1..]); } /// diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index accfea948..7c3562cb2 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -361,34 +361,9 @@ internal class AlphaDecoder : IDisposable { HorizontalUnfilter(null, input, dst, width); } - else if (Vector256.IsHardwareAccelerated) - { - ref byte inputRef = ref MemoryMarshal.GetReference(input); - ref byte prevRef = ref MemoryMarshal.GetReference(prev); - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - - nuint i; - int maxPos = width & ~31; - for (i = 0; i < (uint)maxPos; i += 32) - { - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, i)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref prevRef, i)); - Vector256 c0 = a0.AsByte() + b0.AsByte(); - ref byte outputRef = ref Unsafe.Add(ref dstRef, i); - Unsafe.As>(ref outputRef) = c0; - } - - for (; i < (uint)width; i++) - { - Unsafe.Add(ref dstRef, i) = (byte)(Unsafe.Add(ref prevRef, i) + Unsafe.Add(ref inputRef, i)); - } - } else { - for (int i = 0; i < width; i++) - { - dst[i] = (byte)(prev[i] + input[i]); - } + TensorPrimitives_.Add(input[..width], prev[..width], dst[..width]); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index 03bedfe67..fc3ecdd94 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -3,9 +3,7 @@ using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless; @@ -542,48 +540,7 @@ internal abstract unsafe class Vp8LHistogram DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); - if (Avx2.IsSupported && count >= 32) - { - ref uint aRef = ref MemoryMarshal.GetReference(a); - ref uint bRef = ref MemoryMarshal.GetReference(b); - ref uint outputRef = ref MemoryMarshal.GetReference(output); - - nuint idx = 0; - do - { - // Load values. - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 0)); - Vector256 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 8)); - Vector256 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 16)); - Vector256 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, idx + 24)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 0)); - Vector256 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 8)); - Vector256 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 16)); - Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, idx + 24)); - - // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But - // that's ok since the histogram values are less than 1<<28 (max picture count). - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 0)) = Avx2.Add(a0, b0); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 8)) = Avx2.Add(a1, b1); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 16)) = Avx2.Add(a2, b2); - Unsafe.As>(ref Unsafe.Add(ref outputRef, idx + 24)) = Avx2.Add(a3, b3); - idx += 32; - } - while (idx <= (uint)count - 32); - - int i = (int)idx; - for (; i < count; i++) - { - output[i] = a[i] + b[i]; - } - } - else - { - for (int i = 0; i < count; i++) - { - output[i] = a[i] + b[i]; - } - } + TensorPrimitives_.Add(a[..count], b[..count], output[..count]); } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs new file mode 100644 index 000000000..14802f590 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class AddSpan +{ + private byte[] scalarValues = null!; + private byte[] tensorValues = null!; + private byte[] addends = null!; + + /// + /// Gets or sets the number of values to add. + /// + [Params(32, 257, 2048)] + public int Length { get; set; } + + /// + /// Creates equivalent deterministic inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.scalarValues = new byte[this.Length]; + this.tensorValues = new byte[this.Length]; + this.addends = new byte[this.Length]; + + for (int i = 0; i < this.Length; i++) + { + byte value = (byte)((i * 17) + 31); + this.scalarValues[i] = value; + this.tensorValues[i] = value; + this.addends[i] = (byte)((i * 29) + 7); + } + } + + /// + /// Adds the values with a scalar loop. + /// + /// The first result, which keeps the mutated data observable to the benchmark harness. + [Benchmark(Baseline = true)] + public byte Scalar() + { + for (int i = 0; i < this.scalarValues.Length; i++) + { + this.scalarValues[i] += this.addends[i]; + } + + return this.scalarValues[0]; + } + + /// + /// Adds the values with the tensor compatibility pipeline. + /// + /// The first result, which keeps the mutated data observable to the benchmark harness. + [Benchmark] + public byte TensorPipeline() + { + TensorPrimitives_.Add(this.tensorValues, this.addends, this.tensorValues); + return this.tensorValues[0]; + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs new file mode 100644 index 000000000..bc11fbaff --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class NormalizeSpan +{ + private float[] scalarValues = null!; + private float[] tensorValues = null!; + + /// + /// Gets or sets the number of values to normalize. + /// + [Params(7, 32, 257, 2048)] + public int Length { get; set; } + + /// + /// Creates equivalent deterministic inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.scalarValues = new float[this.Length]; + this.tensorValues = new float[this.Length]; + + for (int i = 0; i < this.scalarValues.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.scalarValues[i] = value; + this.tensorValues[i] = value; + } + } + + /// + /// Normalizes the values with a scalar loop. + /// + /// The first result, which keeps the mutated data observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Scalar() + { + for (int i = 0; i < this.scalarValues.Length; i++) + { + this.scalarValues[i] /= 4096F; + } + + return this.scalarValues[0]; + } + + /// + /// Normalizes the values with the tensor compatibility pipeline. + /// + /// The first result, which keeps the mutated data observable to the benchmark harness. + [Benchmark] + public float TensorPipeline() + { + Numerics.Normalize(this.tensorValues, 4096F); + return this.tensorValues[0]; + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs new file mode 100644 index 000000000..08ac3d09b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs @@ -0,0 +1,813 @@ +// 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 BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +#pragma warning disable SA1649 // File name should match first type name +public class TensorPrimitivesJpegMultiplyAssemblyComparison +#pragma warning restore SA1649 // File name should match first type name +{ + private readonly float multiplier = -1F; + private float[] legacyValues = null!; + private float[] tensorValues = null!; + + /// + /// Creates equivalent stable inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.legacyValues = new float[256]; + this.tensorValues = new float[256]; + + for (int i = 0; i < this.legacyValues.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.legacyValues[i] = value; + this.tensorValues[i] = value; + } + } + + /// + /// Multiplies the row with the retired JPEG AVX pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Legacy() + { + LegacyMultiply(this.legacyValues, this.multiplier); + return this.legacyValues[0]; + } + + /// + /// Multiplies the row with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float Tensor() + { + TensorPrimitives_.Multiply(this.tensorValues, this.multiplier, this.tensorValues); + return this.tensorValues[0]; + } + + /// + /// Reproduces the retired JPEG multiplication loop for assembly comparison. + /// + /// The row to multiply. + /// The scalar multiplier. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyMultiply(Span target, float multiplier) + { + ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + nuint count = (uint)target.Length / (uint)Vector256.Count; + Vector256 multiplierVector = Vector256.Create(multiplier); + + for (nuint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVector, i) = Avx.Multiply(Unsafe.Add(ref targetVector, i), multiplierVector); + } + } +} + +public class TensorPrimitivesNormalizeAssemblyComparison +{ + private readonly float divisor = -1F; + private float[] legacyValues = null!; + private float[] tensorValues = null!; + + /// + /// Creates equivalent stable inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.legacyValues = new float[7]; + this.tensorValues = new float[7]; + + for (int i = 0; i < this.legacyValues.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.legacyValues[i] = value; + this.tensorValues[i] = value; + } + } + + /// + /// Normalizes the values with the retired fixed-width pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Legacy() + { + LegacyNormalize(this.legacyValues, this.divisor); + return this.legacyValues[0]; + } + + /// + /// Normalizes the values with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float Tensor() + { + Numerics.Normalize(this.tensorValues, this.divisor); + return this.tensorValues[0]; + } + + /// + /// Reproduces the retired normalization loop for assembly comparison. + /// + /// The values to normalize. + /// The scalar divisor. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyNormalize(Span span, float sum) + { + ref float start = ref MemoryMarshal.GetReference(span); + ref float vectorEnd = ref Unsafe.Add(ref start, span.Length & ~7); + Vector256 sum256 = Vector256.Create(sum); + + while (Unsafe.IsAddressLessThan(ref start, ref vectorEnd)) + { + Unsafe.As>(ref start) /= sum256; + start = ref Unsafe.Add(ref start, (nuint)8); + } + + if ((span.Length & 7) >= 4) + { + Unsafe.As>(ref start) /= sum256.GetLower(); + start = ref Unsafe.Add(ref start, (nuint)4); + } + + ref float end = ref Unsafe.Add(ref start, span.Length & 3); + + while (Unsafe.IsAddressLessThan(ref start, ref end)) + { + start /= sum; + start = ref Unsafe.Add(ref start, (nuint)1); + } + } +} + +public class TensorPrimitivesUInt32AssemblyComparison +{ + private uint[] x = null!; + private uint[] y = null!; + private uint[] legacyDestination = null!; + private uint[] tensorDestination = null!; + + /// + /// Creates deterministic histogram inputs and independent destinations. + /// + [GlobalSetup] + public void Setup() + { + this.x = new uint[2048]; + this.y = new uint[2048]; + this.legacyDestination = new uint[2048]; + this.tensorDestination = new uint[2048]; + + for (int i = 0; i < this.x.Length; i++) + { + this.x[i] = (uint)((i * 17) + 31); + this.y[i] = (uint)((i * 29) + 7); + } + } + + /// + /// Adds histogram bins with the retired four-vector AVX2 pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public uint Legacy() + { + LegacyAdd(this.x, this.y, this.legacyDestination); + return this.legacyDestination[0]; + } + + /// + /// Adds histogram bins with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public uint Tensor() + { + TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); + return this.tensorDestination[0]; + } + + /// + /// Reproduces the retired WebP histogram addition loop for assembly comparison. + /// + /// The first histogram. + /// The second histogram. + /// The destination receiving the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + ref uint xRef = ref MemoryMarshal.GetReference(x); + ref uint yRef = ref MemoryMarshal.GetReference(y); + ref uint destinationRef = ref MemoryMarshal.GetReference(destination); + + nuint index = 0; + + do + { + Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, index)); + Vector256 x1 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 8)); + Vector256 x2 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 16)); + Vector256 x3 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 24)); + Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, index)); + Vector256 y1 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 8)); + Vector256 y2 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 16)); + Vector256 y3 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 24)); + + Unsafe.As>(ref Unsafe.Add(ref destinationRef, index)) = Avx2.Add(x0, y0); + Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 8)) = Avx2.Add(x1, y1); + Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 16)) = Avx2.Add(x2, y2); + Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 24)) = Avx2.Add(x3, y3); + index += 32; + } + while (index <= (uint)x.Length - 32); + + for (int i = (int)index; i < x.Length; i++) + { + destination[i] = x[i] + y[i]; + } + } +} + +public class TensorPrimitivesByteAssemblyComparison +{ + private byte[] x = null!; + private byte[] y = null!; + private byte[] legacyDestination = null!; + private byte[] tensorDestination = null!; + + /// + /// Creates deterministic byte inputs and independent destinations. + /// + [GlobalSetup] + public void Setup() + { + this.x = new byte[2048]; + this.y = new byte[2048]; + this.legacyDestination = new byte[2048]; + this.tensorDestination = new byte[2048]; + + for (int i = 0; i < this.x.Length; i++) + { + this.x[i] = (byte)((i * 17) + 31); + this.y[i] = (byte)((i * 29) + 7); + } + } + + /// + /// Adds bytes with the retired WebP AVX2 pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public byte Legacy() + { + LegacyAdd(this.x, this.y, this.legacyDestination); + return this.legacyDestination[0]; + } + + /// + /// Adds bytes with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public byte Tensor() + { + TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); + return this.tensorDestination[0]; + } + + /// + /// Reproduces the retired WebP byte addition loop for assembly comparison. + /// + /// The first input. + /// The second input. + /// The destination receiving modulo-256 sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) + { + ref byte xRef = ref MemoryMarshal.GetReference(x); + ref byte yRef = ref MemoryMarshal.GetReference(y); + ref byte destinationRef = ref MemoryMarshal.GetReference(destination); + + nuint i; + int maxPosition = x.Length & ~31; + + for (i = 0; i < (uint)maxPosition; i += 32) + { + Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + Vector256 result = x0.AsByte() + y0.AsByte(); + Unsafe.As>(ref Unsafe.Add(ref destinationRef, i)) = result; + } + + for (; i < (uint)x.Length; i++) + { + Unsafe.Add(ref destinationRef, i) = (byte)(Unsafe.Add(ref xRef, i) + Unsafe.Add(ref yRef, i)); + } + } +} + +public class TensorPrimitivesSingleAddAssemblyComparison +{ + private float[] legacyTarget = null!; + private float[] tensorTarget = null!; + private float[] source = null!; + + /// + /// Creates deterministic JPEG row inputs. + /// + [GlobalSetup] + public void Setup() + { + this.legacyTarget = new float[2048]; + this.tensorTarget = new float[2048]; + this.source = new float[2048]; + + for (int i = 0; i < this.source.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.legacyTarget[i] = value; + this.tensorTarget[i] = value; + this.source[i] = ((i * 29) % 31) - 15; + } + } + + /// + /// Adds JPEG row values with the retired AVX pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Legacy() + { + LegacyAdd(this.legacyTarget, this.source); + return this.legacyTarget[0]; + } + + /// + /// Adds JPEG row values with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float Tensor() + { + TensorPrimitives_.Add(this.tensorTarget, this.source, this.tensorTarget); + return this.tensorTarget[0]; + } + + /// + /// Reproduces the retired JPEG row addition loop for assembly comparison. + /// + /// The destination row. + /// The row added to the destination. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyAdd(Span target, ReadOnlySpan source) + { + ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + nuint count = (uint)source.Length / (uint)Vector256.Count; + + for (nuint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVector, i) = Avx.Add(Unsafe.Add(ref targetVector, i), Unsafe.Add(ref sourceVector, i)); + } + } +} + +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +[GenericTypeArguments(typeof(int))] +[GenericTypeArguments(typeof(float))] +[GenericTypeArguments(typeof(double))] +public class TensorPrimitivesClampAssemblyComparison + where T : unmanaged, INumber +{ + private T[] legacyValues = null!; + private T[] tensorValues = null!; + private T min; + private T max; + + /// + /// Creates deterministic clamp inputs for the current element type. + /// + [GlobalSetup] + public void Setup() + { + this.legacyValues = new T[2048]; + this.tensorValues = new T[2048]; + this.min = T.CreateTruncating(64); + this.max = T.CreateTruncating(128); + + for (int i = 0; i < this.legacyValues.Length; i++) + { + T value = T.CreateTruncating((i * 31) % 257); + this.legacyValues[i] = value; + this.tensorValues[i] = value; + } + } + + /// + /// Clamps values with the retired pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public T Legacy() + { + LegacyClamp(this.legacyValues, this.min, this.max); + return this.legacyValues[0]; + } + + /// + /// Clamps values with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public T Tensor() + { + TensorPrimitives_.Clamp(this.tensorValues, this.min, this.max, this.tensorValues); + return this.tensorValues[0]; + } + + /// + /// Reproduces the retired clamp pipeline for assembly comparison. + /// + /// The values to clamp. + /// The inclusive lower bound. + /// The inclusive upper bound. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyClamp(Span span, T min, T max) + { + int remainder = Numerics.ModuloP2(span.Length, Vector.Count); + int adjustedCount = span.Length - remainder; + + if (adjustedCount > 0) + { + Vector vectorMin = new(min); + Vector vectorMax = new(max); + nint vectorCount = (nint)(uint)adjustedCount / Vector.Count; + nint remainingVectors = Numerics.Modulo4(vectorCount); + nint unrolledVectors = vectorCount - remainingVectors; + + ref Vector current0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); + ref Vector current1 = ref Unsafe.Add(ref current0, 1); + ref Vector current2 = ref Unsafe.Add(ref current0, 2); + ref Vector current3 = ref Unsafe.Add(ref current0, 3); + ref Vector end = ref Unsafe.Add(ref current0, unrolledVectors); + + while (Unsafe.IsAddressLessThan(ref current0, ref end)) + { + current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); + current1 = Vector.Min(Vector.Max(vectorMin, current1), vectorMax); + current2 = Vector.Min(Vector.Max(vectorMin, current2), vectorMax); + current3 = Vector.Min(Vector.Max(vectorMin, current3), vectorMax); + + current0 = ref Unsafe.Add(ref current0, 4); + current1 = ref Unsafe.Add(ref current1, 4); + current2 = ref Unsafe.Add(ref current2, 4); + current3 = ref Unsafe.Add(ref current3, 4); + } + + if (remainingVectors > 0) + { + current0 = ref end; + end = ref Unsafe.Add(ref end, remainingVectors); + + while (Unsafe.IsAddressLessThan(ref current0, ref end)) + { + current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); + current0 = ref Unsafe.Add(ref current0, 1); + } + } + } + + for (int i = adjustedCount; i < span.Length; i++) + { + T value = span[i]; + span[i] = value > max ? max : value < min ? min : value; + } + } +} + +public class TensorPrimitivesIccMaxAssemblyComparison +{ + private Vector4[] legacyValues = null!; + private Vector4[] tensorValues = null!; + + /// + /// Creates deterministic ICC values containing positive and negative channels. + /// + [GlobalSetup] + public void Setup() + { + this.legacyValues = new Vector4[512]; + this.tensorValues = new Vector4[512]; + + for (int i = 0; i < this.legacyValues.Length; i++) + { + float value = ((i * 17) % 251) - 125; + Vector4 vector = new(value, value + 1, value - 1, value + 2); + this.legacyValues[i] = vector; + this.tensorValues[i] = vector; + } + } + + /// + /// Clips negative channels with the retired ICC pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Legacy() + { + for (int i = 0; i < this.legacyValues.Length; i++) + { + this.legacyValues[i] = Vector4.Max(this.legacyValues[i], Vector4.Zero); + } + + return this.legacyValues[0].X; + } + + /// + /// Clips negative channels with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float Tensor() + { + Span values = MemoryMarshal.Cast(this.tensorValues.AsSpan()); + TensorPrimitives_.Max(values, 0F, values); + return values[0]; + } +} + +public class TensorPrimitivesIccMultiplyAssemblyComparison +{ + private readonly float multiplier = 65280F / 65535F; + private Vector4[] source = null!; + private Vector4[] legacyDestination = null!; + private Vector4[] tensorDestination = null!; + + /// + /// Creates deterministic ICC inputs and independent destinations. + /// + [GlobalSetup] + public void Setup() + { + this.source = new Vector4[512]; + this.legacyDestination = new Vector4[512]; + this.tensorDestination = new Vector4[512]; + + for (int i = 0; i < this.source.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.source[i] = new Vector4(value, value + 1, value + 2, value + 3); + } + } + + /// + /// Multiplies ICC channels with the retired pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public float Legacy() + { + Span source = MemoryMarshal.Cast(this.source.AsSpan()); + Span destination = MemoryMarshal.Cast(this.legacyDestination.AsSpan()); + ref Vector sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destinationVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + Vector scale = new(this.multiplier); + nuint count = (uint)source.Length / (uint)Vector.Count; + + for (nuint i = 0; i < count; i++) + { + Unsafe.Add(ref destinationVector, i) = Unsafe.Add(ref sourceVector, i) * scale; + } + + return destination[0]; + } + + /// + /// Multiplies ICC channels with the tensor compatibility pipeline. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float Tensor() + { + Span source = MemoryMarshal.Cast(this.source.AsSpan()); + Span destination = MemoryMarshal.Cast(this.tensorDestination.AsSpan()); + TensorPrimitives_.Multiply(source, this.multiplier, destination); + return destination[0]; + } +} + +#if NET10_0_OR_GREATER +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +[GenericTypeArguments(typeof(float))] +public class TensorPrimitivesRuntimeAddAssemblyComparison + where T : unmanaged, INumber +{ + private T[] x = null!; + private T[] y = null!; + private T[] compatibilityDestination = null!; + private T[] runtimeDestination = null!; + + /// + /// Creates deterministic inputs and independent destinations. + /// + [GlobalSetup] + public void Setup() + { + this.x = new T[2048]; + this.y = new T[2048]; + this.compatibilityDestination = new T[2048]; + this.runtimeDestination = new T[2048]; + + for (int i = 0; i < this.x.Length; i++) + { + this.x[i] = T.CreateTruncating((i * 17) + 31); + this.y[i] = T.CreateTruncating((i * 29) + 7); + } + } + + /// + /// Adds values with the compatibility implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public T Compatibility() + { + TensorPrimitives_.Add(this.x, this.y, this.compatibilityDestination); + return this.compatibilityDestination[0]; + } + + /// + /// Adds values with the .NET runtime implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public T Runtime() + { + System.Numerics.Tensors.TensorPrimitives.Add(this.x, this.y, this.runtimeDestination); + return this.runtimeDestination[0]; + } +} + +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +[GenericTypeArguments(typeof(int))] +[GenericTypeArguments(typeof(float))] +[GenericTypeArguments(typeof(double))] +public class TensorPrimitivesRuntimeClampAssemblyComparison + where T : unmanaged, INumber +{ + private T[] compatibilityValues = null!; + private T[] runtimeValues = null!; + private T min; + private T max; + + /// + /// Creates deterministic inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.compatibilityValues = new T[2048]; + this.runtimeValues = new T[2048]; + this.min = T.CreateTruncating(64); + this.max = T.CreateTruncating(128); + + for (int i = 0; i < this.compatibilityValues.Length; i++) + { + T value = T.CreateTruncating((i * 31) % 257); + this.compatibilityValues[i] = value; + this.runtimeValues[i] = value; + } + } + + /// + /// Clamps values with the compatibility implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark(Baseline = true)] + public T Compatibility() + { + TensorPrimitives_.Clamp(this.compatibilityValues, this.min, this.max, this.compatibilityValues); + return this.compatibilityValues[0]; + } + + /// + /// Clamps values with the .NET runtime implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public T Runtime() + { + System.Numerics.Tensors.TensorPrimitives.Clamp(this.runtimeValues, this.min, this.max, this.runtimeValues); + return this.runtimeValues[0]; + } +} + +public class TensorPrimitivesRuntimeSingleScalarAssemblyComparison +{ + private readonly float scalar = -1F; + private float[] compatibilityValues = null!; + private float[] runtimeValues = null!; + + /// + /// Creates equivalent stable inputs for both implementations. + /// + [GlobalSetup] + public void Setup() + { + this.compatibilityValues = new float[2048]; + this.runtimeValues = new float[2048]; + + for (int i = 0; i < this.compatibilityValues.Length; i++) + { + float value = ((i * 17) % 251) + 1; + this.compatibilityValues[i] = value; + this.runtimeValues[i] = value; + } + } + + /// + /// Divides values with the compatibility implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float CompatibilityDivide() + { + TensorPrimitives_.Divide(this.compatibilityValues, this.scalar, this.compatibilityValues); + return this.compatibilityValues[0]; + } + + /// + /// Divides values with the .NET runtime implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float RuntimeDivide() + { + System.Numerics.Tensors.TensorPrimitives.Divide(this.runtimeValues, this.scalar, this.runtimeValues); + return this.runtimeValues[0]; + } + + /// + /// Computes maximum values with the compatibility implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float CompatibilityMax() + { + TensorPrimitives_.Max(this.compatibilityValues, 0F, this.compatibilityValues); + return this.compatibilityValues[0]; + } + + /// + /// Computes maximum values with the .NET runtime implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float RuntimeMax() + { + System.Numerics.Tensors.TensorPrimitives.Max(this.runtimeValues, 0F, this.runtimeValues); + return this.runtimeValues[0]; + } + + /// + /// Multiplies values with the compatibility implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float CompatibilityMultiply() + { + TensorPrimitives_.Multiply(this.compatibilityValues, this.scalar, this.compatibilityValues); + return this.compatibilityValues[0]; + } + + /// + /// Multiplies values with the .NET runtime implementation. + /// + /// The first result, which keeps the writes observable to the benchmark harness. + [Benchmark] + public float RuntimeMultiply() + { + System.Numerics.Tensors.TensorPrimitives.Multiply(this.runtimeValues, this.scalar, this.runtimeValues); + return this.runtimeValues[0]; + } +} +#endif diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index fa5fdd816..0351ecae9 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -69,6 +69,7 @@ + diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs new file mode 100644 index 000000000..1cc5b943f --- /dev/null +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -0,0 +1,356 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Tests.Common; + +public class TensorPrimitivesTests +{ + /// + /// Gets lengths that exercise scalar execution, every SIMD width, overlapping tails, and the unrolled loop. + /// + public static TheoryData SpanLengths => new() + { + 0, + 1, + 3, + 4, + 5, + 7, + 8, + 9, + 15, + 16, + 17, + 31, + 32, + 33, + 63, + 64, + 65, + 127, + 128, + 129, + 2048 + }; + + /// + /// Verifies that byte addition wraps modulo 256 and supports either input as the in-place destination. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void AddByteMatchesScalarFormula(int length) + { + byte[] x = new byte[length]; + byte[] y = new byte[length]; + byte[] expected = new byte[length]; + + for (int i = 0; i < length; i++) + { + x[i] = (byte)((i * 23) + 197); + y[i] = (byte)((i * 41) + 113); + expected[i] = unchecked((byte)(x[i] + y[i])); + } + + byte[] destination = new byte[length]; + TensorPrimitives_.Add(x, y, destination); + Assert.Equal(expected, destination); + + byte[] xInPlace = (byte[])x.Clone(); + TensorPrimitives_.Add(xInPlace, y, xInPlace); + Assert.Equal(expected, xInPlace); + + byte[] yInPlace = (byte[])y.Clone(); + TensorPrimitives_.Add(x, yInPlace, yInPlace); + Assert.Equal(expected, yInPlace); + } + + /// + /// Verifies that unsigned integer addition preserves unchecked histogram accumulation semantics. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void AddUInt32MatchesScalarFormula(int length) + { + uint[] x = new uint[length]; + uint[] y = new uint[length]; + uint[] expected = new uint[length]; + + for (int i = 0; i < length; i++) + { + x[i] = ((uint)i * 1_234_567U) + 0xF0000000U; + y[i] = ((uint)i * 7_654_321U) + 0x30000000U; + expected[i] = unchecked(x[i] + y[i]); + } + + TensorPrimitives_.Add(x, y, x); + Assert.Equal(expected, x); + } + + /// + /// Verifies that integer clamping produces identical results for separate and in-place destinations. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void ClampInt32MatchesScalarFormula(int length) + { + int[] source = new int[length]; + int[] expected = new int[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = ((i * 37) % 401) - 200; + expected[i] = Math.Clamp(source[i], -73, 91); + } + + int[] destination = new int[length]; + TensorPrimitives_.Clamp(source, -73, 91, destination); + Assert.Equal(expected, destination); + + int[] inPlace = (int[])source.Clone(); + TensorPrimitives_.Clamp(inPlace, -73, 91, inPlace); + Assert.Equal(expected, inPlace); + } + + /// + /// Verifies that floating-point clamping matches the runtime tensor formula for special values and unordered bounds. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void ClampSingleMatchesRuntimeFormula(int length) + { + float[] values = + { + float.NaN, + -0F, + 0F, + -1F, + 1F, + float.NegativeInfinity, + float.PositiveInfinity + }; + + float[] source = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = values[i % values.Length]; + + // Runtime main follows Min(Max(x, min), max) for vectorizable types, including unordered bounds. + expected[i] = float.Min(float.Max(source[i], 2F), -2F); + } + + TensorPrimitives_.Clamp(source, 2F, -2F, source); + AssertSingleBitsEqual(expected, source); + } + + /// + /// Verifies that single-precision clamping preserves the runtime's signed-zero and NaN behavior. + /// + [Fact] + public void ClampSinglePreservesRuntimeSpecialValueSemantics() + { + float[] values = + { + float.NaN, + float.NegativeInfinity, + -0F, + 0F, + float.PositiveInfinity + }; + + float[] actual = new float[129]; + float[] expected = new float[actual.Length]; + + for (int i = 0; i < actual.Length; i++) + { + actual[i] = values[i % values.Length]; + expected[i] = float.Min(float.Max(actual[i], -0F), 0F); + } + + TensorPrimitives_.Clamp(actual, -0F, 0F, actual); + AssertSingleBitsEqual(expected, actual); + } + + /// + /// Verifies that double-precision clamping preserves the runtime's signed-zero and NaN behavior. + /// + [Fact] + public void ClampDoublePreservesRuntimeSpecialValueSemantics() + { + double[] values = + { + double.NaN, + double.NegativeInfinity, + -0D, + 0D, + double.PositiveInfinity + }; + + double[] actual = new double[65]; + double[] expected = new double[actual.Length]; + + for (int i = 0; i < actual.Length; i++) + { + actual[i] = values[i % values.Length]; + expected[i] = double.Min(double.Max(actual[i], -0D), 0D); + } + + TensorPrimitives_.Clamp(actual, -0D, 0D, actual); + AssertDoubleBitsEqual(expected, actual); + } + + /// + /// Verifies that division produces identical results for separate and in-place destinations. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void DivideSingleMatchesScalarFormula(int length) + { + float[] source = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = (i - 65.25F) * 1.75F; + expected[i] = source[i] / 3.25F; + } + + float[] destination = new float[length]; + TensorPrimitives_.Divide(source, 3.25F, destination); + AssertSingleBitsEqual(expected, destination); + + float[] inPlace = (float[])source.Clone(); + TensorPrimitives_.Divide(inPlace, 3.25F, inPlace); + AssertSingleBitsEqual(expected, inPlace); + } + + /// + /// Verifies that maximum selection preserves the runtime's NaN and signed-zero semantics. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void MaxSingleMatchesRuntimeFormula(int length) + { + float[] values = + { + float.NaN, + float.NegativeInfinity, + -1F, + -0F, + 0F, + 1F, + float.PositiveInfinity + }; + + float[] actual = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < length; i++) + { + actual[i] = values[i % values.Length]; + expected[i] = float.Max(actual[i], -0F); + } + + TensorPrimitives_.Max(actual, -0F, actual); + AssertSingleBitsEqual(expected, actual); + } + + /// + /// Verifies that multiplication produces identical results for separate and in-place destinations. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void MultiplySingleMatchesScalarFormula(int length) + { + float[] source = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = (i - 65.25F) * 1.75F; + expected[i] = source[i] * 0.375F; + } + + float[] destination = new float[length]; + TensorPrimitives_.Multiply(source, 0.375F, destination); + AssertSingleBitsEqual(expected, destination); + + TensorPrimitives_.Multiply(source, 0.375F, source); + AssertSingleBitsEqual(expected, source); + } + + /// + /// Verifies that the normalization compatibility call preserves its element-wise division contract. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void NormalizeMatchesScalarFormula(int length) + { + float[] actual = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < actual.Length; i++) + { + actual[i] = (i + 1) * 0.125F; + expected[i] = actual[i] / 7.5F; + } + + Numerics.Normalize(actual, 7.5F); + AssertSingleBitsEqual(expected, actual); + } + + /// + /// Compares floating-point results while preserving signed-zero behavior. + /// + /// The expected values. + /// The actual values. + private static void AssertSingleBitsEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + Assert.Equal(expected.Length, actual.Length); + + for (int i = 0; i < expected.Length; i++) + { + if (float.IsNaN(expected[i])) + { + Assert.True(float.IsNaN(actual[i])); + } + else + { + Assert.Equal(BitConverter.SingleToInt32Bits(expected[i]), BitConverter.SingleToInt32Bits(actual[i])); + } + } + } + + /// + /// Compares double-precision results while preserving signed-zero behavior. + /// + /// The expected values. + /// The actual values. + private static void AssertDoubleBitsEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + Assert.Equal(expected.Length, actual.Length); + + for (int i = 0; i < expected.Length; i++) + { + if (double.IsNaN(expected[i])) + { + Assert.True(double.IsNaN(actual[i])); + } + else + { + Assert.Equal(BitConverter.DoubleToInt64Bits(expected[i]), BitConverter.DoubleToInt64Bits(actual[i])); + } + } + } +} From e9db3675e92222a99a52dd423f90997d29d8b780 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 02:25:02 +1000 Subject: [PATCH 221/240] Normalize JPEG color conversion SIMD --- .../JpegColorConverter.CmykOperator.cs | 245 ++++++++ .../JpegColorConverter.GrayScaleOperator.cs | 203 ++++++ .../JpegColorConverter.Operator.cs | 594 ++++++++++++++++++ .../JpegColorConverter.RgbOperator.cs | 184 ++++++ .../JpegColorConverter.TiffCmykOperator.cs | 159 +++++ .../JpegColorConverter.TiffYccKOperator.cs | 187 ++++++ .../JpegColorConverter.YCbCrOperator.cs | 263 ++++++++ .../JpegColorConverter.YccKOperator.cs | 135 ++++ .../ColorConverters/JpegColorConverterBase.cs | 155 +---- .../ColorConversionBenchmark.cs | 1 + .../JpegColorConverterOperatorComparison.cs | 239 +++++++ .../JpegColorConverterTraversalAssembly.cs | 325 ++++++++++ .../ColorConversion/YCbCrOperatorAssembly.cs | 244 +++++++ .../YCbCrOperatorComparison.cs | 127 ++++ .../Formats/Jpg/JpegColorConverterTests.cs | 354 +++++++++-- 15 files changed, 3217 insertions(+), 198 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs new file mode 100644 index 000000000..48281c34a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements inverted JPEG CMYK conversion for scalar and SIMD lanes. + /// + internal readonly struct CmykOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.Cmyk; + + /// + public static int ComponentCount => 4; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref float c0, + ref float c1, + ref float c2, + float c3, + float maximumValue, + float halfValue, + float scale) + { + // Adobe-style CMYK stores inverted component samples. Multiplying K by scale twice folds the + // two sample-domain divisions into one factor before it modulates the C, M, and Y planes. + float scaledK = c3 * scale * scale; + c0 *= scaledK; + c1 *= scaledK; + c2 *= scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector128 c0, + ref Vector128 c1, + ref Vector128 c2, + Vector128 c3, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale) + { + // Each K lane supplies the common modulation factor for the corresponding C, M, and Y lanes. + Vector128 scaledK = c3 * scale * scale; + c0 *= scaledK; + c1 *= scaledK; + c2 *= scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector256 c0, + ref Vector256 c1, + ref Vector256 c2, + Vector256 c3, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale) + { + // Eight independent CMYK samples remain lane-aligned throughout the modulation. + Vector256 scaledK = c3 * scale * scale; + c0 *= scaledK; + c1 *= scaledK; + c2 *= scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector512 c0, + ref Vector512 c1, + ref Vector512 c2, + Vector512 c3, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale) + { + // Sixteen independent CMYK samples remain lane-aligned throughout the modulation. + Vector512 scaledK = c3 * scale * scale; + c0 *= scaledK; + c1 *= scaledK; + c2 *= scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + float r, + float g, + float b, + float maximumValue, + float halfValue, + float scale, + out float c0, + out float c1, + out float c2, + out float c3) + { + float c = maximumValue - r; + float m = maximumValue - g; + float y = maximumValue - b; + float k = MathF.Min(c, MathF.Min(m, y)); + + // Pure black makes the chromatic divisor zero. In that case chromatic ink is defined as zero; + // otherwise remove K and normalize the remaining C, M, and Y contributions. + if (k >= maximumValue) + { + c = 0; + m = 0; + y = 0; + } + else + { + // The same remaining range normalizes every chromatic channel. Computing its reciprocal once + // replaces three divisions with one division and three multiplies. + float reciprocal = 1F / (maximumValue - k); + c = (c - k) * reciprocal; + m = (m - k) * reciprocal; + y = (y - k) * reciprocal; + } + + // JPEG CMYK is inverted, including K, so normalized chromatic values are reflected around max. + c0 = maximumValue - (c * maximumValue); + c1 = maximumValue - (m * maximumValue); + c2 = maximumValue - (y * maximumValue); + c3 = maximumValue - k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector128 r, + Vector128 g, + Vector128 b, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3) + { + Vector128 c = maximumValue - r; + Vector128 m = maximumValue - g; + Vector128 y = maximumValue - b; + Vector128 k = Vector128.Min(c, Vector128.Min(m, y)); + + // The all-bits mask clears the undefined zero-divisor result for pure-black lanes without a branch. + Vector128 nonBlack = ~Vector128.Equals(k, maximumValue); + Vector128 reciprocal = Vector128.One / (maximumValue - k); + c = ((c - k) * reciprocal) & nonBlack; + m = ((m - k) * reciprocal) & nonBlack; + y = ((y - k) * reciprocal) & nonBlack; + + c0 = maximumValue - (c * maximumValue); + c1 = maximumValue - (m * maximumValue); + c2 = maximumValue - (y * maximumValue); + c3 = maximumValue - k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector256 r, + Vector256 g, + Vector256 b, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3) + { + Vector256 c = maximumValue - r; + Vector256 m = maximumValue - g; + Vector256 y = maximumValue - b; + Vector256 k = Vector256.Min(c, Vector256.Min(m, y)); + + // Masking preserves lane independence when a vector mixes pure black with chromatic pixels. + Vector256 nonBlack = ~Vector256.Equals(k, maximumValue); + Vector256 reciprocal = Vector256.One / (maximumValue - k); + c = ((c - k) * reciprocal) & nonBlack; + m = ((m - k) * reciprocal) & nonBlack; + y = ((y - k) * reciprocal) & nonBlack; + + c0 = maximumValue - (c * maximumValue); + c1 = maximumValue - (m * maximumValue); + c2 = maximumValue - (y * maximumValue); + c3 = maximumValue - k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector512 r, + Vector512 g, + Vector512 b, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3) + { + Vector512 c = maximumValue - r; + Vector512 m = maximumValue - g; + Vector512 y = maximumValue - b; + Vector512 k = Vector512.Min(c, Vector512.Min(m, y)); + + // AVX-512 still uses a full floating-point mask value here because bitwise clearing exactly matches + // the narrower operator semantics and lets the JIT select the most suitable native instructions. + Vector512 nonBlack = ~Vector512.Equals(k, maximumValue); + Vector512 reciprocal = Vector512.One / (maximumValue - k); + c = ((c - k) * reciprocal) & nonBlack; + m = ((m - k) * reciprocal) & nonBlack; + y = ((y - k) * reciprocal) & nonBlack; + + c0 = maximumValue - (c * maximumValue); + c1 = maximumValue - (m * maximumValue); + c2 = maximumValue - (y * maximumValue); + c3 = maximumValue - k; + } + + /// + public static void ConvertToRgbInPlaceWithIcc( + Configuration configuration, + IccProfile profile, + in ComponentValues values, + float maximumValue) + => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs new file mode 100644 index 000000000..2ac827da7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs @@ -0,0 +1,203 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements grayscale expansion and RGB luminance reduction for scalar and SIMD lanes. + /// + internal readonly struct GrayScaleOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.Grayscale; + + /// + public static int ComponentCount => 1; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref float c0, + ref float c1, + ref float c2, + float c3, + float maximumValue, + float halfValue, + float scale) + { + // JPEG stores luminance in the integer sample domain. Normalize it once, then duplicate the + // same value into all three RGB planes. Keeping it local also prevents potentially aliasing + // byref stores from forcing the JIT to reload c0 between assignments. + float luminance = c0 * scale; + c0 = luminance; + c1 = luminance; + c2 = luminance; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector128 c0, + ref Vector128 c1, + ref Vector128 c2, + Vector128 c3, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale) + { + // Each XMM lane is one independent luminance sample. Reusing the normalized vector for R, G, + // and B avoids recomputing the scale and keeps it live across potentially aliasing byref stores. + Vector128 luminance = c0 * scale; + c0 = luminance; + c1 = luminance; + c2 = luminance; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector256 c0, + ref Vector256 c1, + ref Vector256 c2, + Vector256 c3, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale) + { + // Eight luminance samples occupy the YMM lanes. The local retains the normalized vector across + // all three output stores even when the destination planes alias. + Vector256 luminance = c0 * scale; + c0 = luminance; + c1 = luminance; + c2 = luminance; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector512 c0, + ref Vector512 c1, + ref Vector512 c2, + Vector512 c3, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale) + { + // Sixteen luminance samples occupy the ZMM lanes. The local retains the normalized vector across + // all three output stores without shuffles, interleaving, or source reloads. + Vector512 luminance = c0 * scale; + c0 = luminance; + c1 = luminance; + c2 = luminance; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + float r, + float g, + float b, + float maximumValue, + float halfValue, + float scale, + out float c0, + out float c1, + out float c2, + out float c3) + { + // Rec.601 luma weights operate directly in the encoder sample domain. Only c0 is stored for a + // one-component model; the remaining out values exist solely to satisfy the common operator shape. + c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); + c1 = 0; + c2 = 0; + c3 = 0; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector128 r, + Vector128 g, + Vector128 b, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3) + { + // The nested estimate gives each pixel the same multiply-add grouping as the scalar Rec.601 formula. + c0 = Vector128_.MultiplyAddEstimate( + Vector128.Create(0.299F), + r, + Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); + c1 = default; + c2 = default; + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector256 r, + Vector256 g, + Vector256 b, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3) + { + // YMM lanes evaluate the same Rec.601 equation independently, with no horizontal lane reduction. + c0 = Vector256_.MultiplyAddEstimate( + Vector256.Create(0.299F), + r, + Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); + c1 = default; + c2 = default; + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector512 r, + Vector512 g, + Vector512 b, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3) + { + // ZMM lanes retain the same arithmetic order as narrower paths so only SIMD width changes. + c0 = Vector512_.MultiplyAddEstimate( + Vector512.Create(0.299F), + r, + Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); + c1 = default; + c2 = default; + c3 = default; + } + + /// + public static void ConvertToRgbInPlaceWithIcc( + Configuration configuration, + IccProfile profile, + in ComponentValues values, + float maximumValue) + => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs new file mode 100644 index 000000000..be9cedcb0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs @@ -0,0 +1,594 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Defines the color-model-specific arithmetic used by . + /// + /// + /// Each overload describes the same lane-wise transform. The generic traversal selects the widest + /// available overload and the JIT resolves these static interface calls for each closed converter type. + /// + internal interface IJpegColorConverterOperator + { + /// + /// Gets the JPEG color space handled by the operator. + /// + static abstract JpegColorSpace ColorSpace { get; } + + /// + /// Gets the number of component planes used by the color space. + /// + static abstract int ComponentCount { get; } + + /// + /// Converts one JPEG sample to normalized RGB. + /// + /// The first component, replaced by red. + /// The second component, replaced by green. + /// The third component, replaced by blue. + /// The fourth component, or zero for a three-component color space. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of . + static abstract void ConvertToRgb( + ref float c0, + ref float c1, + ref float c2, + float c3, + float maximumValue, + float halfValue, + float scale); + + /// + /// Converts four JPEG samples to normalized RGB. + /// + /// The first component lanes, replaced by red. + /// The second component lanes, replaced by green. + /// The third component lanes, replaced by blue. + /// The fourth component lanes, or zero for a three-component color space. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + static abstract void ConvertToRgb( + ref Vector128 c0, + ref Vector128 c1, + ref Vector128 c2, + Vector128 c3, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale); + + /// + /// Converts eight JPEG samples to normalized RGB. + /// + /// The first component lanes, replaced by red. + /// The second component lanes, replaced by green. + /// The third component lanes, replaced by blue. + /// The fourth component lanes, or zero for a three-component color space. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + static abstract void ConvertToRgb( + ref Vector256 c0, + ref Vector256 c1, + ref Vector256 c2, + Vector256 c3, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale); + + /// + /// Converts sixteen JPEG samples to normalized RGB. + /// + /// The first component lanes, replaced by red. + /// The second component lanes, replaced by green. + /// The third component lanes, replaced by blue. + /// The fourth component lanes, or zero for a three-component color space. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + static abstract void ConvertToRgb( + ref Vector512 c0, + ref Vector512 c1, + ref Vector512 c2, + Vector512 c3, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale); + + /// + /// Converts one RGB sample to JPEG components. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of . + /// The first converted component. + /// The second converted component. + /// The third converted component. + /// The fourth converted component, if used. + static abstract void ConvertFromRgb( + float r, + float g, + float b, + float maximumValue, + float halfValue, + float scale, + out float c0, + out float c1, + out float c2, + out float c3); + + /// + /// Converts four RGB samples to JPEG components. + /// + /// The red lanes. + /// The green lanes. + /// The blue lanes. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + /// The first converted component lanes. + /// The second converted component lanes. + /// The third converted component lanes. + /// The fourth converted component lanes, if used. + static abstract void ConvertFromRgb( + Vector128 r, + Vector128 g, + Vector128 b, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3); + + /// + /// Converts eight RGB samples to JPEG components. + /// + /// The red lanes. + /// The green lanes. + /// The blue lanes. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + /// The first converted component lanes. + /// The second converted component lanes. + /// The third converted component lanes. + /// The fourth converted component lanes, if used. + static abstract void ConvertFromRgb( + Vector256 r, + Vector256 g, + Vector256 b, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3); + + /// + /// Converts sixteen RGB samples to JPEG components. + /// + /// The red lanes. + /// The green lanes. + /// The blue lanes. + /// The maximum component value for the configured precision. + /// The midpoint component value for the configured precision. + /// The reciprocal of in every lane. + /// The first converted component lanes. + /// The second converted component lanes. + /// The third converted component lanes. + /// The fourth converted component lanes, if used. + static abstract void ConvertFromRgb( + Vector512 r, + Vector512 g, + Vector512 b, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3); + + /// + /// Converts JPEG component values to RGB using the supplied ICC profile. + /// + /// The configuration used to allocate temporary storage. + /// The source ICC profile. + /// The component values to convert. + /// The maximum component value for the configured precision. + static abstract void ConvertToRgbInPlaceWithIcc( + Configuration configuration, + IccProfile profile, + in ComponentValues values, + float maximumValue); + } + + /// + /// Converts a JPEG color model using a single operator-driven traversal for all SIMD widths. + /// + /// The color-model-specific arithmetic. + internal sealed class JpegColorConverter : JpegColorConverterBase + where TOperator : struct, IJpegColorConverterOperator + { + /// + /// Initializes a new instance of the class. + /// + /// The precision in bits. + public JpegColorConverter(int precision) + : base(TOperator.ColorSpace, precision) + { + } + + /// + public override bool IsAvailable => true; + + /// + public override int ElementsPerBatch + => Vector512.IsHardwareAccelerated + ? Vector512.Count + : Vector256.IsHardwareAccelerated + ? Vector256.Count + : Vector128.IsHardwareAccelerated + ? Vector128.Count + : 1; + + /// + public override void ConvertToRgbInPlace(in ComponentValues values) + { + // JPEG component processors own equally sized planar buffers. Capturing their first elements + // as byrefs lets every width share the same offset without introducing Span bounds checks in + // the hot loops. Component3 may be empty; its byref is only dereferenced for four-component operators. + ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); + ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); + ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); + ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); + + int length = values.Component0.Length; + int i = 0; + float scale = 1F / this.MaximumValue; + + // Descending widths keep one traversal while allowing an AVX-512 machine to process + // an eight-pixel JPEG block with AVX2 rather than sending the entire block to scalar code. + if (Vector512.IsHardwareAccelerated) + { + // Subtracting the lane count turns the loop condition into a single signed comparison. + // A negative value naturally skips this width, and i <= end proves every unaligned + // 64-byte reinterpretation remains entirely inside its component buffer. + int oneVectorFromEnd = length - Vector512.Count; + + if (i <= oneVectorFromEnd) + { + // Precision-derived values are broadcast only when this width has work. Keeping them outside + // the loop avoids repeated setup without penalizing rows handled entirely by narrower widths. + Vector512 maximumValue = Vector512.Create(this.MaximumValue); + Vector512 halfValue = Vector512.Create(this.HalfValue); + Vector512 scaleVector = Vector512.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector512.Count) + { + ref Vector512 c0 = ref Unsafe.As>(ref Unsafe.Add(ref c0Base, i)); + ref Vector512 c1 = ref Unsafe.As>(ref Unsafe.Add(ref c1Base, i)); + ref Vector512 c2 = ref Unsafe.As>(ref Unsafe.Add(ref c2Base, i)); + + // ComponentCount is a static property on the closed operator type, so the JIT removes + // this choice. Three-component models never dereference the empty Component3 byref. + Vector512 c3 = TOperator.ComponentCount == 4 + ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) + : default; + + // c0-c2 alias the planar source vectors and are replaced in place with normalized RGB. + // c3 is passed by value because the fourth JPEG component must remain unchanged. + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); + } + } + } + + if (Vector256.IsHardwareAccelerated) + { + // The shared offset continues where AVX-512 stopped. At this point fewer than sixteen + // samples remain, so this stage consumes the complete eight-sample remainder when present. + int oneVectorFromEnd = length - Vector256.Count; + + if (i <= oneVectorFromEnd) + { + // YMM precision state is materialized only for an eight-sample remainder or an AVX2-only loop. + Vector256 maximumValue = Vector256.Create(this.MaximumValue); + Vector256 halfValue = Vector256.Create(this.HalfValue); + Vector256 scaleVector = Vector256.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector256.Count) + { + ref Vector256 c0 = ref Unsafe.As>(ref Unsafe.Add(ref c0Base, i)); + ref Vector256 c1 = ref Unsafe.As>(ref Unsafe.Add(ref c1Base, i)); + ref Vector256 c2 = ref Unsafe.As>(ref Unsafe.Add(ref c2Base, i)); + + // The closed operator makes this a compile-time color-model choice, not a per-vector + // runtime abstraction or interface dispatch. + Vector256 c3 = TOperator.ComponentCount == 4 + ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) + : default; + + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); + } + } + } + + if (Vector128.IsHardwareAccelerated) + { + // SSE/AdvSimd handles the final four complete samples. This also gives non-AVX machines + // the same traversal without duplicating the control flow for another register width. + int oneVectorFromEnd = length - Vector128.Count; + + if (i <= oneVectorFromEnd) + { + // XMM state is likewise created only when four samples remain for this stage. + Vector128 maximumValue = Vector128.Create(this.MaximumValue); + Vector128 halfValue = Vector128.Create(this.HalfValue); + Vector128 scaleVector = Vector128.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + ref Vector128 c0 = ref Unsafe.As>(ref Unsafe.Add(ref c0Base, i)); + ref Vector128 c1 = ref Unsafe.As>(ref Unsafe.Add(ref c1Base, i)); + ref Vector128 c2 = ref Unsafe.As>(ref Unsafe.Add(ref c2Base, i)); + + // As at the wider stages, the fourth vector is loaded only for CMYK-shaped operators. + Vector128 c3 = TOperator.ComponentCount == 4 + ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) + : default; + + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); + } + } + } + + // Fewer than four samples remain after the SIMD cascade. Processing from the shared offset + // guarantees each sample is visited exactly once for arbitrary test lengths and JPEG block rows. + for (; i < length; i++) + { + float c3 = TOperator.ComponentCount == 4 ? Unsafe.Add(ref c3Base, i) : 0; + + TOperator.ConvertToRgb( + ref Unsafe.Add(ref c0Base, i), + ref Unsafe.Add(ref c1Base, i), + ref Unsafe.Add(ref c2Base, i), + c3, + this.MaximumValue, + this.HalfValue, + scale); + } + } + + /// + public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) + => TOperator.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // The encoder supplies equally sized RGB planes and destination component planes. Byrefs preserve + // contiguous access and allow the same proven vector boundary to govern every participating lane. + // Component3 is empty for three-component formats and is only written by four-component operators. + ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); + ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); + ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); + ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); + ref float rBase = ref MemoryMarshal.GetReference(rLane); + ref float gBase = ref MemoryMarshal.GetReference(gLane); + ref float bBase = ref MemoryMarshal.GetReference(bLane); + + int length = values.Component0.Length; + int i = 0; + float scale = 1F / this.MaximumValue; + + // Each vector overload returns planar component vectors. Storing them here keeps the + // operator concerned only with color arithmetic and preserves contiguous lane access. + if (Vector512.IsHardwareAccelerated) + { + // The end offset proves all three 64-byte RGB reads and all component writes are in range. + // A short row yields a negative end and falls through to the next supported width. + int oneVectorFromEnd = length - Vector512.Count; + + if (i <= oneVectorFromEnd) + { + // Operators receive width-matched precision state only when this width has work, keeping + // invariant broadcasts outside the loop without charging narrower or scalar rows for them. + Vector512 maximumValue = Vector512.Create(this.MaximumValue); + Vector512 halfValue = Vector512.Create(this.HalfValue); + Vector512 scaleVector = Vector512.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector512.Count) + { + Vector512 r = Unsafe.As>(ref Unsafe.Add(ref rBase, i)); + Vector512 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); + Vector512 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); + + TOperator.ConvertFromRgb( + r, + g, + b, + maximumValue, + halfValue, + scaleVector, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3); + + // Outputs remain planar: each vector contains sixteen consecutive samples from one + // JPEG component. Static count checks prevent grayscale from touching absent planes + // while disappearing completely from three- and four-component specializations. + Unsafe.As>(ref Unsafe.Add(ref c0Base, i)) = c0; + + if (TOperator.ComponentCount >= 2) + { + Unsafe.As>(ref Unsafe.Add(ref c1Base, i)) = c1; + } + + if (TOperator.ComponentCount >= 3) + { + Unsafe.As>(ref Unsafe.Add(ref c2Base, i)) = c2; + } + + if (TOperator.ComponentCount >= 4) + { + Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) = c3; + } + } + } + } + + if (Vector256.IsHardwareAccelerated) + { + // Continue from the AVX-512 offset so an eight-sample tail stays vectorized on AVX-512 CPUs. + int oneVectorFromEnd = length - Vector256.Count; + + if (i <= oneVectorFromEnd) + { + // Materialize YMM state only for an eight-sample remainder or an AVX2-only loop. + Vector256 maximumValue = Vector256.Create(this.MaximumValue); + Vector256 halfValue = Vector256.Create(this.HalfValue); + Vector256 scaleVector = Vector256.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector256.Count) + { + Vector256 r = Unsafe.As>(ref Unsafe.Add(ref rBase, i)); + Vector256 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); + Vector256 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); + + TOperator.ConvertFromRgb( + r, + g, + b, + maximumValue, + halfValue, + scaleVector, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3); + + // Static count checks write only planes owned by this color model. + Unsafe.As>(ref Unsafe.Add(ref c0Base, i)) = c0; + + if (TOperator.ComponentCount >= 2) + { + Unsafe.As>(ref Unsafe.Add(ref c1Base, i)) = c1; + } + + if (TOperator.ComponentCount >= 3) + { + Unsafe.As>(ref Unsafe.Add(ref c2Base, i)) = c2; + } + + if (TOperator.ComponentCount >= 4) + { + Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) = c3; + } + } + } + } + + if (Vector128.IsHardwareAccelerated) + { + // The final SIMD stage consumes four complete RGB samples on SSE or AdvSimd hardware. + int oneVectorFromEnd = length - Vector128.Count; + + if (i <= oneVectorFromEnd) + { + // Materialize XMM state only when the final SIMD stage can consume four samples. + Vector128 maximumValue = Vector128.Create(this.MaximumValue); + Vector128 halfValue = Vector128.Create(this.HalfValue); + Vector128 scaleVector = Vector128.Create(scale); + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + Vector128 r = Unsafe.As>(ref Unsafe.Add(ref rBase, i)); + Vector128 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); + Vector128 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); + + TOperator.ConvertFromRgb( + r, + g, + b, + maximumValue, + halfValue, + scaleVector, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3); + + // Four results are stored only for the planes represented by the closed operator. + Unsafe.As>(ref Unsafe.Add(ref c0Base, i)) = c0; + + if (TOperator.ComponentCount >= 2) + { + Unsafe.As>(ref Unsafe.Add(ref c1Base, i)) = c1; + } + + if (TOperator.ComponentCount >= 3) + { + Unsafe.As>(ref Unsafe.Add(ref c2Base, i)) = c2; + } + + if (TOperator.ComponentCount >= 4) + { + Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) = c3; + } + } + } + } + + // Scalar conversion is reserved for the zero-to-three samples that cannot fill Vector128. + for (; i < length; i++) + { + TOperator.ConvertFromRgb( + Unsafe.Add(ref rBase, i), + Unsafe.Add(ref gBase, i), + Unsafe.Add(ref bBase, i), + this.MaximumValue, + this.HalfValue, + scale, + out float c0, + out float c1, + out float c2, + out float c3); + + Unsafe.Add(ref c0Base, i) = c0; + + if (TOperator.ComponentCount >= 2) + { + Unsafe.Add(ref c1Base, i) = c1; + } + + if (TOperator.ComponentCount >= 3) + { + Unsafe.Add(ref c2Base, i) = c2; + } + + if (TOperator.ComponentCount >= 4) + { + Unsafe.Add(ref c3Base, i) = c3; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs new file mode 100644 index 000000000..6d7281dc6 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs @@ -0,0 +1,184 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements direct JPEG RGB normalization and planar RGB copying for scalar and SIMD lanes. + /// + internal readonly struct RgbOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.RGB; + + /// + public static int ComponentCount => 3; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref float c0, + ref float c1, + ref float c2, + float c3, + float maximumValue, + float halfValue, + float scale) + { + // The JPEG planes already represent R, G, and B. Conversion therefore consists only of moving + // each integer-domain sample into the normalized floating-point domain consumed by pixel packing. + c0 *= scale; + c1 *= scale; + c2 *= scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector128 c0, + ref Vector128 c1, + ref Vector128 c2, + Vector128 c3, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale) + { + // Four samples from each planar channel remain in their lanes while sharing one normalization vector. + c0 *= scale; + c1 *= scale; + c2 *= scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector256 c0, + ref Vector256 c1, + ref Vector256 c2, + Vector256 c3, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale) + { + // Eight samples per plane are normalized independently without channel shuffles. + c0 *= scale; + c1 *= scale; + c2 *= scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector512 c0, + ref Vector512 c1, + ref Vector512 c2, + Vector512 c3, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale) + { + // Sixteen samples per plane are normalized independently without changing planar ordering. + c0 *= scale; + c1 *= scale; + c2 *= scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + float r, + float g, + float b, + float maximumValue, + float halfValue, + float scale, + out float c0, + out float c1, + out float c2, + out float c3) + { + // Encoder RGB lanes already use the JPEG sample domain, so the direct color model copies them. + c0 = r; + c1 = g; + c2 = b; + c3 = 0; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector128 r, + Vector128 g, + Vector128 b, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3) + { + // The planar vectors map one-to-one to JPEG components; the fourth result is statically discarded. + c0 = r; + c1 = g; + c2 = b; + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector256 r, + Vector256 g, + Vector256 b, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3) + { + // The planar vectors map one-to-one to JPEG components; no arithmetic or rearrangement is required. + c0 = r; + c1 = g; + c2 = b; + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector512 r, + Vector512 g, + Vector512 b, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3) + { + // The widest path is likewise a register-to-register planar copy for sixteen pixels. + c0 = r; + c1 = g; + c2 = b; + c3 = default; + } + + /// + public static void ConvertToRgbInPlaceWithIcc( + Configuration configuration, + IccProfile profile, + in ComponentValues values, + float maximumValue) + => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs new file mode 100644 index 000000000..88b6c2411 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements non-inverted TIFF JPEG CMYK conversion for scalar and SIMD lanes. + /// + internal readonly struct TiffCmykOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.TiffCmyk; + + /// + public static int ComponentCount => 4; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) + { + // TIFF stores conventional CMYK rather than Adobe's inverted representation. Normalize every + // component, invert C/M/Y, and let the remaining light after K modulate each RGB channel. + float k = 1F - (c3 * scale); + c0 = (1F - (c0 * scale)) * k; + c1 = (1F - (c1 * scale)) * k; + c2 = (1F - (c2 * scale)) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) + { + // K remains lane-aligned with its C/M/Y sample while one-minus performs the non-inverted CMYK mapping. + Vector128 k = Vector128.One - (c3 * scale); + c0 = (Vector128.One - (c0 * scale)) * k; + c1 = (Vector128.One - (c1 * scale)) * k; + c2 = (Vector128.One - (c2 * scale)) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) + { + // Eight conventional CMYK samples convert independently without channel rearrangement. + Vector256 k = Vector256.One - (c3 * scale); + c0 = (Vector256.One - (c0 * scale)) * k; + c1 = (Vector256.One - (c1 * scale)) * k; + c2 = (Vector256.One - (c2 * scale)) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) + { + // Sixteen conventional CMYK samples convert independently without channel rearrangement. + Vector512 k = Vector512.One - (c3 * scale); + c0 = (Vector512.One - (c0 * scale)) * k; + c1 = (Vector512.One - (c1 * scale)) * k; + c2 = (Vector512.One - (c2 * scale)) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) + { + float c = maximumValue - r; + float m = maximumValue - g; + float y = maximumValue - b; + float k = MathF.Min(c, MathF.Min(m, y)); + + // Removing the shared black contribution requires division by the remaining range. Pure black + // consumes that range completely, so its chromatic components are defined as zero. + if (k >= maximumValue) + { + c = 0; + m = 0; + y = 0; + } + else + { + // One reciprocal normalizes C, M, and Y against their shared remaining range. + float reciprocal = 1F / (maximumValue - k); + c = (c - k) * reciprocal; + m = (m - k) * reciprocal; + y = (y - k) * reciprocal; + } + + // TIFF stores conventional CMYK: scale normalized C/M/Y back into the sample domain and retain K. + c0 = c * maximumValue; + c1 = m * maximumValue; + c2 = y * maximumValue; + c3 = k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) + { + Vector128 c = maximumValue - r; + Vector128 m = maximumValue - g; + Vector128 y = maximumValue - b; + Vector128 k = Vector128.Min(c, Vector128.Min(m, y)); + + // The all-bits mask clears the undefined zero-divisor result only in pure-black lanes. + Vector128 nonBlack = ~Vector128.Equals(k, maximumValue); + Vector128 reciprocal = Vector128.One / (maximumValue - k); + c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; + c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; + c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; + c3 = k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) + { + Vector256 c = maximumValue - r; + Vector256 m = maximumValue - g; + Vector256 y = maximumValue - b; + Vector256 k = Vector256.Min(c, Vector256.Min(m, y)); + + // Eight lanes independently clear the pure-black singularity before returning conventional CMYK. + Vector256 nonBlack = ~Vector256.Equals(k, maximumValue); + Vector256 reciprocal = Vector256.One / (maximumValue - k); + c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; + c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; + c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; + c3 = k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) + { + Vector512 c = maximumValue - r; + Vector512 m = maximumValue - g; + Vector512 y = maximumValue - b; + Vector512 k = Vector512.Min(c, Vector512.Min(m, y)); + + // Sixteen lanes retain the same branchless singularity handling and component layout. + Vector512 nonBlack = ~Vector512.Equals(k, maximumValue); + Vector512 reciprocal = Vector512.One / (maximumValue - k); + c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; + c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; + c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; + c3 = k; + } + + /// + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) + => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs new file mode 100644 index 000000000..c66d266a1 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements non-inverted TIFF JPEG YccK conversion for scalar and SIMD lanes. + /// + internal readonly struct TiffYccKOperator : IJpegColorConverterOperator + { + private const float SourceScale = 1F / 255F; + + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.TiffYccK; + + /// + public static int ComponentCount => 4; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) + { + float y = c0 * scale; + float cb = (c1 - halfValue) * scale; + float cr = (c2 - halfValue) * scale; + float k = 1F - (c3 * scale); + + // TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the + // remaining light after K modulate all three channels. + c0 = (y + (YCbCrScalar.RCrMult * cr)) * k; + c1 = (y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr)) * k; + c2 = (y + (YCbCrScalar.BCbMult * cb)) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) + { + Vector128 y = c0 * scale; + Vector128 cb = (c1 - halfValue) * scale; + Vector128 cr = (c2 - halfValue) * scale; + Vector128 k = Vector128.One - (c3 * scale); + + // Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. + c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y) * k; + c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)) * k; + c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) + { + Vector256 y = c0 * scale; + Vector256 cb = (c1 - halfValue) * scale; + Vector256 cr = (c2 - halfValue) * scale; + Vector256 k = Vector256.One - (c3 * scale); + + // Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. + c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y) * k; + c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)) * k; + c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) + { + Vector512 y = c0 * scale; + Vector512 cb = (c1 - halfValue) * scale; + Vector512 cr = (c2 - halfValue) * scale; + Vector512 k = Vector512.One - (c3 * scale); + + // Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. + c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y) * k; + c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)) * k; + c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y) * k; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) + { + r *= SourceScale; + g *= SourceScale; + b *= SourceScale; + float k = 1F - MathF.Max(r, MathF.Max(g, b)); + + // Dividing by the brightest channel removes K before YCbCr projection. Pure black has no + // chromatic direction, so it maps to zero luma and the neutral chroma midpoint. + if (k >= 1F) + { + c0 = 0; + c1 = halfValue; + c2 = halfValue; + c3 = maximumValue; + return; + } + + float divisor = 1F / (1F - k); + r *= divisor; + g *= divisor; + b *= divisor; + c0 = ((0.299F * r) + (0.587F * g) + (0.114F * b)) * maximumValue; + c1 = halfValue + (((-0.168736F * r) + (-0.331264F * g) + (0.5F * b)) * maximumValue); + c2 = halfValue + (((0.5F * r) + (-0.418688F * g) + (-0.081312F * b)) * maximumValue); + c3 = k * maximumValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) + { + Vector128 sourceScale = Vector128.Create(SourceScale); + r *= sourceScale; + g *= sourceScale; + b *= sourceScale; + Vector128 k = Vector128.One - Vector128.Max(r, Vector128.Max(g, b)); + + // The mask assigns no chromatic direction to pure-black lanes while preserving neighboring pixels. + Vector128 nonBlack = ~Vector128.Equals(k, Vector128.One); + Vector128 divisor = Vector128.One / (Vector128.One - k); + r = (r * divisor) & nonBlack; + g = (g * divisor) & nonBlack; + b = (b * divisor) & nonBlack; + c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)) * maximumValue; + c1 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)) * maximumValue); + c2 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)) * maximumValue); + c3 = k * maximumValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) + { + Vector256 sourceScale = Vector256.Create(SourceScale); + r *= sourceScale; + g *= sourceScale; + b *= sourceScale; + Vector256 k = Vector256.One - Vector256.Max(r, Vector256.Max(g, b)); + + // Eight lanes normalize chromatic direction independently and retain neutral chroma for black. + Vector256 nonBlack = ~Vector256.Equals(k, Vector256.One); + Vector256 divisor = Vector256.One / (Vector256.One - k); + r = (r * divisor) & nonBlack; + g = (g * divisor) & nonBlack; + b = (b * divisor) & nonBlack; + c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)) * maximumValue; + c1 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)) * maximumValue); + c2 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)) * maximumValue); + c3 = k * maximumValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) + { + Vector512 sourceScale = Vector512.Create(SourceScale); + r *= sourceScale; + g *= sourceScale; + b *= sourceScale; + Vector512 k = Vector512.One - Vector512.Max(r, Vector512.Max(g, b)); + + // Sixteen lanes normalize chromatic direction independently and retain neutral chroma for black. + Vector512 nonBlack = ~Vector512.Equals(k, Vector512.One); + Vector512 divisor = Vector512.One / (Vector512.One - k); + r = (r * divisor) & nonBlack; + g = (g * divisor) & nonBlack; + b = (b * divisor) & nonBlack; + c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)) * maximumValue; + c1 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)) * maximumValue); + c2 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue); + c3 = k * maximumValue; + } + + /// + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) + => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs new file mode 100644 index 000000000..ca8671e9f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs @@ -0,0 +1,263 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements the JPEG YCbCr conversion formula for scalar and SIMD lanes. + /// + internal readonly struct YCbCrOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr; + + /// + public static int ComponentCount => 3; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref float c0, + ref float c1, + ref float c2, + float c3, + float maximumValue, + float halfValue, + float scale) + { + float y = c0; + float cb = c1 - halfValue; + float cr = c2 - halfValue; + + // c0/c1/c2 initially mean Y/Cb/Cr. Chroma is centered around zero before applying + // the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized + // to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the + // destination pixel format owns saturation; retaining overshoot avoids discarding color information. + c0 = MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1 = MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2 = MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector128 c0, + ref Vector128 c1, + ref Vector128 c2, + Vector128 c3, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale) + { + Vector128 y = c0; + Vector128 cb = c1 - halfValue; + Vector128 cr = c2 - halfValue; + + // Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available: + // R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample + // domain before the common normalization scale so all precisions use integer JPEG sample semantics. + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); + Vector128 g = Vector128_.MultiplyAddEstimate( + cr, + Vector128.Create(-YCbCrScalar.GCrMult), + Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + + c0 = Vector128_.RoundToNearestInteger(r) * scale; + c1 = Vector128_.RoundToNearestInteger(g) * scale; + c2 = Vector128_.RoundToNearestInteger(b) * scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector256 c0, + ref Vector256 c1, + ref Vector256 c2, + Vector256 c3, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale) + { + Vector256 y = c0; + Vector256 cb = c1 - halfValue; + Vector256 cr = c2 - halfValue; + + // These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload. + // Keeping an explicit overload allows the JIT to emit native YMM operations without a width + // switch or decomposing the vector into smaller values. + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); + Vector256 g = Vector256_.MultiplyAddEstimate( + cr, + Vector256.Create(-YCbCrScalar.GCrMult), + Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + + c0 = Vector256_.RoundToNearestInteger(r) * scale; + c1 = Vector256_.RoundToNearestInteger(g) * scale; + c2 = Vector256_.RoundToNearestInteger(b) * scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb( + ref Vector512 c0, + ref Vector512 c1, + ref Vector512 c2, + Vector512 c3, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale) + { + Vector512 y = c0; + Vector512 cb = c1 - halfValue; + Vector512 cr = c2 - halfValue; + + // Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts; + // assembly inspection verifies the JIT hoists them from the loop and retains fused operations. + // The formula and rounding order remain identical to the narrower overloads. + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); + Vector512 g = Vector512_.MultiplyAddEstimate( + cr, + Vector512.Create(-YCbCrScalar.GCrMult), + Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + + c0 = Vector512_.RoundToNearestInteger(r) * scale; + c1 = Vector512_.RoundToNearestInteger(g) * scale; + c2 = Vector512_.RoundToNearestInteger(b) * scale; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + float r, + float g, + float b, + float maximumValue, + float halfValue, + float scale, + out float c0, + out float c1, + out float c2, + out float c3) + { + // The RGB inputs are unnormalized 0..255 encoder lanes. The BT.601 luma weights form Y, + // while the signed chroma projections are biased by halfValue into the JPEG sample domain. + // YCbCr has no fourth component, so c3 is a compile-time-unused placeholder for the shared loop. + c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); + c1 = halfValue - (0.168736F * r) - (0.331264F * g) + (0.5F * b); + c2 = halfValue + (0.5F * r) - (0.418688F * g) - (0.081312F * b); + c3 = 0; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector128 r, + Vector128 g, + Vector128 b, + Vector128 maximumValue, + Vector128 halfValue, + Vector128 scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3) + { + // Each vector holds four consecutive values from one RGB plane. The nested multiply-add sequence + // produces four Y lanes, four Cb lanes, and four Cr lanes without transposition. The association + // exposes two FMA opportunities per output while preserving the scalar formula's term grouping. + c0 = Vector128_.MultiplyAddEstimate( + Vector128.Create(0.299F), + r, + Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); + c1 = halfValue + Vector128_.MultiplyAddEstimate( + Vector128.Create(-0.168736F), + r, + Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)); + c2 = halfValue + Vector128_.MultiplyAddEstimate( + Vector128.Create(0.5F), + r, + Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)); + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector256 r, + Vector256 g, + Vector256 b, + Vector256 maximumValue, + Vector256 halfValue, + Vector256 scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3) + { + // Eight planar RGB samples use the identical association as Vector128, allowing direct YMM FMA + // generation while preserving the component-per-vector output layout. + c0 = Vector256_.MultiplyAddEstimate( + Vector256.Create(0.299F), + r, + Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); + c1 = halfValue + Vector256_.MultiplyAddEstimate( + Vector256.Create(-0.168736F), + r, + Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)); + c2 = halfValue + Vector256_.MultiplyAddEstimate( + Vector256.Create(0.5F), + r, + Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)); + c3 = default; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb( + Vector512 r, + Vector512 g, + Vector512 b, + Vector512 maximumValue, + Vector512 halfValue, + Vector512 scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3) + { + // Sixteen planar RGB samples use the same nested form. Constants are lane broadcasts and c3 is + // deliberately zero because the shared traversal removes the unused fourth store for this operator. + c0 = Vector512_.MultiplyAddEstimate( + Vector512.Create(0.299F), + r, + Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); + c1 = halfValue + Vector512_.MultiplyAddEstimate( + Vector512.Create(-0.168736F), + r, + Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)); + c2 = halfValue + Vector512_.MultiplyAddEstimate( + Vector512.Create(0.5F), + r, + Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); + c3 = default; + } + + /// + public static void ConvertToRgbInPlaceWithIcc( + Configuration configuration, + IccProfile profile, + in ComponentValues values, + float maximumValue) + => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs new file mode 100644 index 000000000..ef7185dd3 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Implements inverted JPEG YccK conversion for scalar and SIMD lanes. + /// + internal readonly struct YccKOperator : IJpegColorConverterOperator + { + /// + public static JpegColorSpace ColorSpace => JpegColorSpace.Ycck; + + /// + public static int ComponentCount => 4; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) + { + float y = c0; + float cb = c1 - halfValue; + float cr = c2 - halfValue; + float scaledK = c3 * scale * scale; + + // YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before + // subtracting from max and applying K because changing that order changes encoded JPEG semantics. + c0 = (maximumValue - MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1 = (maximumValue - MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2 = (maximumValue - MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) + { + Vector128 y = c0; + Vector128 cb = c1 - halfValue; + Vector128 cr = c2 - halfValue; + Vector128 scaledK = c3 * scale * scale; + + // Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane. + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK; + c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK; + c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) + { + Vector256 y = c0; + Vector256 cb = c1 - halfValue; + Vector256 cr = c2 - halfValue; + Vector256 scaledK = c3 * scale * scale; + + // Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB. + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK; + c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK; + c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) + { + Vector512 y = c0; + Vector512 cb = c1 - halfValue; + Vector512 cr = c2 - halfValue; + Vector512 scaledK = c3 * scale * scale; + + // Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code. + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK; + c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK; + c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) + { + // CMYK extraction supplies inverted chromatic samples and K. Reflecting the first three results + // reconstructs the chromatic RGB that YCbCr encodes, while K passes through untouched. + CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out float c, out float m, out float y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) + { + // Static constrained calls inline both stages, keeping four pixels in registers without materializing CMYK planes. + CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector128 c, out Vector128 m, out Vector128 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) + { + // Eight pixels flow through CMYK extraction and YCbCr projection entirely in YMM registers. + CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector256 c, out Vector256 m, out Vector256 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) + { + // Sixteen pixels flow through both mathematical stages in registers without materializing intermediate planes. + CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector512 c, out Vector512 m, out Vector512 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); + } + + /// + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) + => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 74227c7a6..26e4c3584 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -248,161 +248,50 @@ internal abstract partial class JpegColorConverterBase /// Returns the s for the YCbCr colorspace. /// /// The precision in bits. - private static JpegColorConverterBase GetYCbCrConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new YCbCrVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new YCbCrVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new YCbCrVector128(precision); - } - - return new YCbCrScalar(precision); - } + private static JpegColorConverter GetYCbCrConverter(int precision) + => new JpegColorConverter(precision); /// /// Returns the s for the YccK colorspace. /// /// The precision in bits. - private static JpegColorConverterBase GetYccKConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new YccKVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new YccKVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new YccKVector128(precision); - } - - return new YccKScalar(precision); - } + private static JpegColorConverter GetYccKConverter(int precision) + => new JpegColorConverter(precision); /// /// Returns the s for the CMYK colorspace. /// /// The precision in bits. - private static JpegColorConverterBase GetCmykConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new CmykVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new CmykVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new CmykVector128(precision); - } - - return new CmykScalar(precision); - } + private static JpegColorConverter GetCmykConverter(int precision) + => new JpegColorConverter(precision); /// /// Returns the s for the gray scale colorspace. /// /// The precision in bits. - private static JpegColorConverterBase GetGrayScaleConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new GrayScaleVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new GrayScaleVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new GrayScaleVector128(precision); - } - - return new GrayScaleScalar(precision); - } + private static JpegColorConverter GetGrayScaleConverter(int precision) + => new JpegColorConverter(precision); /// /// Returns the s for the RGB colorspace. /// /// The precision in bits. - private static JpegColorConverterBase GetRgbConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new RgbVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new RgbVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new RgbVector128(precision); - } + private static JpegColorConverter GetRgbConverter(int precision) + => new JpegColorConverter(precision); - return new RgbScalar(precision); - } - - private static JpegColorConverterBase GetTiffCmykConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new TiffCmykVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new TiffCmykVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new TiffCmykVector128(precision); - } - - return new TiffCmykScalar(precision); - } - - private static JpegColorConverterBase GetTiffYccKConverter(int precision) - { - if (JpegColorConverterVector512.IsSupported) - { - return new TiffYccKVector512(precision); - } - - if (JpegColorConverterVector256.IsSupported) - { - return new TiffYccKVector256(precision); - } - - if (JpegColorConverterVector128.IsSupported) - { - return new TiffYccKVector128(precision); - } + /// + /// Returns the for non-inverted TIFF CMYK. + /// + /// The precision in bits. + private static JpegColorConverter GetTiffCmykConverter(int precision) + => new JpegColorConverter(precision); - return new TiffYccKScalar(precision); - } + /// + /// Returns the for non-inverted TIFF YccK. + /// + /// The precision in bits. + private static JpegColorConverter GetTiffYccKConverter(int precision) + => new JpegColorConverter(precision); /// /// A stack-only struct to reference the input buffers using -s. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs index 436aa9bcd..106bb5ff6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs @@ -51,6 +51,7 @@ public abstract class ColorConversionBenchmark // no need to dispose when buffer is not array owner buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); + values.CopyTo(buffers[i].DangerousGetRowSpan(0)); } return buffers; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs new file mode 100644 index 000000000..b614583ca --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs @@ -0,0 +1,239 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Compares each shared operator converter with the Vector512 converter it replaces. +/// +[Config(typeof(Config.Standard))] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class JpegColorConverterOperatorComparison +{ + private JpegColorConverterBase legacy; + private JpegColorConverterBase operatorConverter; + private float[] legacyC0; + private float[] legacyC1; + private float[] legacyC2; + private float[] legacyC3; + private float[] operatorC0; + private float[] operatorC1; + private float[] operatorC2; + private float[] operatorC3; + private float[] r; + private float[] g; + private float[] b; + private int componentCount; + + /// + /// Gets or sets the color model measured by the current benchmark case. + /// + [Params( + JpegColorModel.Grayscale, + JpegColorModel.Rgb, + JpegColorModel.Cmyk, + JpegColorModel.YCbCr, + JpegColorModel.YccK, + JpegColorModel.TiffCmyk, + JpegColorModel.TiffYccK)] + public JpegColorModel ColorModel { get; set; } + + /// + /// Gets or sets the number of pixels converted by each invocation. + /// + [Params(128, 1024)] + public int Count { get; set; } + + /// + /// Creates equivalent legacy and operator converters and their independent component buffers. + /// + [GlobalSetup] + public void Setup() + { + (JpegColorConverterBase Legacy, JpegColorConverterBase Operator, int ComponentCount) converters = + this.ColorModel switch + { + JpegColorModel.Grayscale => ( + new JpegColorConverterBase.GrayScaleVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 1), + JpegColorModel.Rgb => ( + new JpegColorConverterBase.RgbVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 3), + JpegColorModel.Cmyk => ( + new JpegColorConverterBase.CmykVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 4), + JpegColorModel.YCbCr => ( + new JpegColorConverterBase.YCbCrVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 3), + JpegColorModel.YccK => ( + new JpegColorConverterBase.YccKVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 4), + JpegColorModel.TiffCmyk => ( + new JpegColorConverterBase.TiffCmykVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 4), + JpegColorModel.TiffYccK => ( + new JpegColorConverterBase.TiffYccKVector512(8), + new JpegColorConverterBase.JpegColorConverter(8), + 4), + _ => throw new InvalidOperationException(), + }; + + (this.legacy, this.operatorConverter, this.componentCount) = converters; + + Random random = new(42); + this.legacyC0 = CreateRandomValues(this.Count, random); + this.legacyC1 = CreateRandomValues(this.Count, random); + this.legacyC2 = CreateRandomValues(this.Count, random); + this.legacyC3 = CreateRandomValues(this.Count, random); + this.operatorC0 = this.legacyC0.ToArray(); + this.operatorC1 = this.legacyC1.ToArray(); + this.operatorC2 = this.legacyC2.ToArray(); + this.operatorC3 = this.legacyC3.ToArray(); + this.r = CreateRandomValues(this.Count, random); + this.g = CreateRandomValues(this.Count, random); + this.b = CreateRandomValues(this.Count, random); + } + + /// + /// Converts JPEG components to RGB using the replaced Vector512 implementation. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("ToRgb")] + public void LegacyToRgb() + { + JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); + + this.legacy.ConvertToRgbInPlace(values); + } + + /// + /// Converts JPEG components to RGB using the shared operator traversal. + /// + [Benchmark] + [BenchmarkCategory("ToRgb")] + public void OperatorToRgb() + { + JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); + + this.operatorConverter.ConvertToRgbInPlace(values); + } + + /// + /// Converts RGB to JPEG components using the replaced Vector512 implementation. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("FromRgb")] + public void LegacyFromRgb() + { + JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); + + this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); + } + + /// + /// Converts RGB to JPEG components using the shared operator traversal. + /// + [Benchmark] + [BenchmarkCategory("FromRgb")] + public void OperatorFromRgb() + { + JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); + + this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); + } + + /// + /// Creates a component view over the buffers owned by the legacy converter. + /// + /// The component view for the configured color model. + private JpegColorConverterBase.ComponentValues CreateLegacyValues() + => new( + this.componentCount, + this.legacyC0, + this.componentCount > 1 ? this.legacyC1 : this.legacyC0, + this.componentCount > 2 ? this.legacyC2 : this.legacyC0, + this.componentCount > 3 ? this.legacyC3 : []); + + /// + /// Creates a component view over the buffers owned by the operator converter. + /// + /// The component view for the configured color model. + private JpegColorConverterBase.ComponentValues CreateOperatorValues() + => new( + this.componentCount, + this.operatorC0, + this.componentCount > 1 ? this.operatorC1 : this.operatorC0, + this.componentCount > 2 ? this.operatorC2 : this.operatorC0, + this.componentCount > 3 ? this.operatorC3 : []); + + /// + /// Creates deterministic sample-domain values for one component plane. + /// + /// The number of samples to create. + /// The deterministic random source shared by setup. + /// The populated component plane. + private static float[] CreateRandomValues(int length, Random random) + { + float[] values = new float[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (float)random.NextDouble() * 255F; + } + + return values; + } + + /// + /// Identifies the JPEG color model used by a benchmark case. + /// + public enum JpegColorModel + { + /// + /// One luminance component. + /// + Grayscale, + + /// + /// Three direct RGB components. + /// + Rgb, + + /// + /// Four inverted Adobe CMYK components. + /// + Cmyk, + + /// + /// Three JPEG YCbCr components. + /// + YCbCr, + + /// + /// Four inverted Adobe YCCK components. + /// + YccK, + + /// + /// Four non-inverted TIFF CMYK components. + /// + TiffCmyk, + + /// + /// Four non-inverted TIFF YCCK components. + /// + TiffYccK, + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs new file mode 100644 index 000000000..4ec7db8a1 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs @@ -0,0 +1,325 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Exposes every closed JPEG operator traversal beside the Vector512 implementation it replaces. +/// +/// +/// A 63-pixel buffer leaves 256-bit, 128-bit, and scalar remainders after the 512-bit loop, making +/// every operator overload visible in the generated traversal assembly on AVX-512 hardware. +/// +[Config(typeof(Config.Analysis))] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class JpegColorConverterTraversalAssembly +{ + private const int Count = 63; + + private readonly JpegColorConverterBase.GrayScaleVector512 grayscaleLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter grayscaleOperator = new(8); + private readonly JpegColorConverterBase.RgbVector512 rgbLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter rgbOperator = new(8); + private readonly JpegColorConverterBase.CmykVector512 cmykLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter cmykOperator = new(8); + private readonly JpegColorConverterBase.YCbCrVector512 yCbCrLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter yCbCrOperator = new(8); + private readonly JpegColorConverterBase.YccKVector512 yccKLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter yccKOperator = new(8); + private readonly JpegColorConverterBase.TiffCmykVector512 tiffCmykLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter tiffCmykOperator = new(8); + private readonly JpegColorConverterBase.TiffYccKVector512 tiffYccKLegacy = new(8); + private readonly JpegColorConverterBase.JpegColorConverter tiffYccKOperator = new(8); + + private readonly float[] legacyC0 = new float[Count]; + private readonly float[] legacyC1 = new float[Count]; + private readonly float[] legacyC2 = new float[Count]; + private readonly float[] legacyC3 = new float[Count]; + private readonly float[] operatorC0 = new float[Count]; + private readonly float[] operatorC1 = new float[Count]; + private readonly float[] operatorC2 = new float[Count]; + private readonly float[] operatorC3 = new float[Count]; + private readonly float[] r = new float[Count]; + private readonly float[] g = new float[Count]; + private readonly float[] b = new float[Count]; + + /// + /// Populates the component and RGB planes with deterministic sample-domain values. + /// + [GlobalSetup] + public void Setup() + { + Random random = new(42); + + for (int i = 0; i < Count; i++) + { + // Independent non-constant lanes prevent the JIT from folding arithmetic or mask decisions. + this.legacyC0[i] = this.operatorC0[i] = (float)random.NextDouble() * 255F; + this.legacyC1[i] = this.operatorC1[i] = (float)random.NextDouble() * 255F; + this.legacyC2[i] = this.operatorC2[i] = (float)random.NextDouble() * 255F; + this.legacyC3[i] = this.operatorC3[i] = (float)random.NextDouble() * 255F; + this.r[i] = (float)random.NextDouble() * 255F; + this.g[i] = (float)random.NextDouble() * 255F; + this.b[i] = (float)random.NextDouble() * 255F; + } + } + + /// + /// Runs the replaced grayscale component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Grayscale.ToRgb")] + public void GrayscaleLegacyToRgb() + => this.grayscaleLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(1)); + + /// + /// Runs the shared grayscale component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("Grayscale.ToRgb")] + public void GrayscaleOperatorToRgb() + => this.grayscaleOperator.ConvertToRgbInPlace(this.CreateOperatorValues(1)); + + /// + /// Runs the replaced grayscale RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Grayscale.FromRgb")] + public void GrayscaleLegacyFromRgb() + => this.grayscaleLegacy.ConvertFromRgb(this.CreateLegacyValues(1), this.r, this.g, this.b); + + /// + /// Runs the shared grayscale RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("Grayscale.FromRgb")] + public void GrayscaleOperatorFromRgb() + => this.grayscaleOperator.ConvertFromRgb(this.CreateOperatorValues(1), this.r, this.g, this.b); + + /// + /// Runs the replaced RGB component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Rgb.ToRgb")] + public void RgbLegacyToRgb() + => this.rgbLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); + + /// + /// Runs the shared RGB component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("Rgb.ToRgb")] + public void RgbOperatorToRgb() + => this.rgbOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); + + /// + /// Runs the replaced RGB RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Rgb.FromRgb")] + public void RgbLegacyFromRgb() + => this.rgbLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); + + /// + /// Runs the shared RGB RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("Rgb.FromRgb")] + public void RgbOperatorFromRgb() + => this.rgbOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); + + /// + /// Runs the replaced CMYK component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Cmyk.ToRgb")] + public void CmykLegacyToRgb() + => this.cmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); + + /// + /// Runs the shared CMYK component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("Cmyk.ToRgb")] + public void CmykOperatorToRgb() + => this.cmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); + + /// + /// Runs the replaced CMYK RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("Cmyk.FromRgb")] + public void CmykLegacyFromRgb() + => this.cmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); + + /// + /// Runs the shared CMYK RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("Cmyk.FromRgb")] + public void CmykOperatorFromRgb() + => this.cmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); + + /// + /// Runs the replaced YCbCr component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("YCbCr.ToRgb")] + public void YCbCrLegacyToRgb() + => this.yCbCrLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); + + /// + /// Runs the shared YCbCr component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("YCbCr.ToRgb")] + public void YCbCrOperatorToRgb() + => this.yCbCrOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); + + /// + /// Runs the replaced YCbCr RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("YCbCr.FromRgb")] + public void YCbCrLegacyFromRgb() + => this.yCbCrLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); + + /// + /// Runs the shared YCbCr RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("YCbCr.FromRgb")] + public void YCbCrOperatorFromRgb() + => this.yCbCrOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); + + /// + /// Runs the replaced YCCK component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("YccK.ToRgb")] + public void YccKLegacyToRgb() + => this.yccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); + + /// + /// Runs the shared YCCK component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("YccK.ToRgb")] + public void YccKOperatorToRgb() + => this.yccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); + + /// + /// Runs the replaced YCCK RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("YccK.FromRgb")] + public void YccKLegacyFromRgb() + => this.yccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); + + /// + /// Runs the shared YCCK RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("YccK.FromRgb")] + public void YccKOperatorFromRgb() + => this.yccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); + + /// + /// Runs the replaced TIFF CMYK component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TiffCmyk.ToRgb")] + public void TiffCmykLegacyToRgb() + => this.tiffCmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); + + /// + /// Runs the shared TIFF CMYK component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("TiffCmyk.ToRgb")] + public void TiffCmykOperatorToRgb() + => this.tiffCmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); + + /// + /// Runs the replaced TIFF CMYK RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TiffCmyk.FromRgb")] + public void TiffCmykLegacyFromRgb() + => this.tiffCmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); + + /// + /// Runs the shared TIFF CMYK RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("TiffCmyk.FromRgb")] + public void TiffCmykOperatorFromRgb() + => this.tiffCmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); + + /// + /// Runs the replaced TIFF YCCK component-to-RGB traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TiffYccK.ToRgb")] + public void TiffYccKLegacyToRgb() + => this.tiffYccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); + + /// + /// Runs the shared TIFF YCCK component-to-RGB traversal. + /// + [Benchmark] + [BenchmarkCategory("TiffYccK.ToRgb")] + public void TiffYccKOperatorToRgb() + => this.tiffYccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); + + /// + /// Runs the replaced TIFF YCCK RGB-to-component traversal. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TiffYccK.FromRgb")] + public void TiffYccKLegacyFromRgb() + => this.tiffYccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); + + /// + /// Runs the shared TIFF YCCK RGB-to-component traversal. + /// + [Benchmark] + [BenchmarkCategory("TiffYccK.FromRgb")] + public void TiffYccKOperatorFromRgb() + => this.tiffYccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); + + /// + /// Creates a correctly aliased component view over the legacy planes. + /// + /// The number of component planes owned by the color model. + /// The legacy component view. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JpegColorConverterBase.ComponentValues CreateLegacyValues(int componentCount) + => new( + componentCount, + this.legacyC0, + componentCount > 1 ? this.legacyC1 : this.legacyC0, + componentCount > 2 ? this.legacyC2 : this.legacyC0, + componentCount > 3 ? this.legacyC3 : []); + + /// + /// Creates a correctly aliased component view over the operator planes. + /// + /// The number of component planes owned by the color model. + /// The operator component view. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JpegColorConverterBase.ComponentValues CreateOperatorValues(int componentCount) + => new( + componentCount, + this.operatorC0, + componentCount > 1 ? this.operatorC1 : this.operatorC0, + componentCount > 2 ? this.operatorC2 : this.operatorC0, + componentCount > 3 ? this.operatorC3 : []); +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs new file mode 100644 index 000000000..b67ee76df --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs @@ -0,0 +1,244 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Exposes every YCbCr operator overload directly to the disassembly diagnoser. +/// +[Config(typeof(Config.Analysis))] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class YCbCrOperatorAssembly +{ + private const float MaximumValue = 255F; + private const float HalfValue = 128F; + private const float Scale = 1F / MaximumValue; + + private float scalarC0 = 64F; + private float scalarC1 = 96F; + private float scalarC2 = 160F; + + private readonly Vector128 vector128C0 = Vector128.Create(64F); + private readonly Vector128 vector128C1 = Vector128.Create(96F); + private readonly Vector128 vector128C2 = Vector128.Create(160F); + private readonly Vector128 vector128Maximum = Vector128.Create(MaximumValue); + private readonly Vector128 vector128Half = Vector128.Create(HalfValue); + private readonly Vector128 vector128Scale = Vector128.Create(Scale); + + private readonly Vector256 vector256C0 = Vector256.Create(64F); + private readonly Vector256 vector256C1 = Vector256.Create(96F); + private readonly Vector256 vector256C2 = Vector256.Create(160F); + private readonly Vector256 vector256Maximum = Vector256.Create(MaximumValue); + private readonly Vector256 vector256Half = Vector256.Create(HalfValue); + private readonly Vector256 vector256Scale = Vector256.Create(Scale); + + private readonly Vector512 vector512C0 = Vector512.Create(64F); + private readonly Vector512 vector512C1 = Vector512.Create(96F); + private readonly Vector512 vector512C2 = Vector512.Create(160F); + private readonly Vector512 vector512Maximum = Vector512.Create(MaximumValue); + private readonly Vector512 vector512Half = Vector512.Create(HalfValue); + private readonly Vector512 vector512Scale = Vector512.Create(Scale); + + /// + /// Invokes the scalar JPEG-to-RGB operator. + /// + /// A checksum containing all three converted channels. + [Benchmark] + [BenchmarkCategory("ToRgb")] + public float ToRgbScalar() + { + float c0 = this.scalarC0; + float c1 = this.scalarC1; + float c2 = this.scalarC2; + + JpegColorConverterBase.YCbCrOperator.ConvertToRgb( + ref c0, + ref c1, + ref c2, + 0, + MaximumValue, + HalfValue, + Scale); + + // Returning the channel sum keeps every output live in the generated assembly. + return c0 + c1 + c2; + } + + /// + /// Invokes the Vector128 JPEG-to-RGB operator. + /// + /// A checksum containing all three converted channel vectors. + [Benchmark] + [BenchmarkCategory("ToRgb")] + public Vector128 ToRgbVector128() + { + Vector128 c0 = this.vector128C0; + Vector128 c1 = this.vector128C1; + Vector128 c2 = this.vector128C2; + + JpegColorConverterBase.YCbCrOperator.ConvertToRgb( + ref c0, + ref c1, + ref c2, + default, + this.vector128Maximum, + this.vector128Half, + this.vector128Scale); + + // The vector sum makes all RGB results observable without adding stores to the measured body. + return c0 + c1 + c2; + } + + /// + /// Invokes the Vector256 JPEG-to-RGB operator. + /// + /// A checksum containing all three converted channel vectors. + [Benchmark] + [BenchmarkCategory("ToRgb")] + public Vector256 ToRgbVector256() + { + Vector256 c0 = this.vector256C0; + Vector256 c1 = this.vector256C1; + Vector256 c2 = this.vector256C2; + + JpegColorConverterBase.YCbCrOperator.ConvertToRgb( + ref c0, + ref c1, + ref c2, + default, + this.vector256Maximum, + this.vector256Half, + this.vector256Scale); + + // The vector sum makes all RGB results observable without adding stores to the measured body. + return c0 + c1 + c2; + } + + /// + /// Invokes the Vector512 JPEG-to-RGB operator. + /// + /// A checksum containing all three converted channel vectors. + [Benchmark] + [BenchmarkCategory("ToRgb")] + public Vector512 ToRgbVector512() + { + Vector512 c0 = this.vector512C0; + Vector512 c1 = this.vector512C1; + Vector512 c2 = this.vector512C2; + + JpegColorConverterBase.YCbCrOperator.ConvertToRgb( + ref c0, + ref c1, + ref c2, + default, + this.vector512Maximum, + this.vector512Half, + this.vector512Scale); + + // The vector sum makes all RGB results observable without adding stores to the measured body. + return c0 + c1 + c2; + } + + /// + /// Invokes the scalar RGB-to-JPEG operator. + /// + /// A checksum containing all converted components. + [Benchmark] + [BenchmarkCategory("FromRgb")] + public float FromRgbScalar() + { + JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( + this.scalarC0, + this.scalarC1, + this.scalarC2, + MaximumValue, + HalfValue, + Scale, + out float c0, + out float c1, + out float c2, + out float c3); + + // c3 is deliberately included so a future four-component implementation remains observable. + return c0 + c1 + c2 + c3; + } + + /// + /// Invokes the Vector128 RGB-to-JPEG operator. + /// + /// A checksum containing all converted component vectors. + [Benchmark] + [BenchmarkCategory("FromRgb")] + public Vector128 FromRgbVector128() + { + JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( + this.vector128C0, + this.vector128C1, + this.vector128C2, + this.vector128Maximum, + this.vector128Half, + this.vector128Scale, + out Vector128 c0, + out Vector128 c1, + out Vector128 c2, + out Vector128 c3); + + // Include all planar results in the returned vector so the JIT retains every calculation. + return c0 + c1 + c2 + c3; + } + + /// + /// Invokes the Vector256 RGB-to-JPEG operator. + /// + /// A checksum containing all converted component vectors. + [Benchmark] + [BenchmarkCategory("FromRgb")] + public Vector256 FromRgbVector256() + { + JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( + this.vector256C0, + this.vector256C1, + this.vector256C2, + this.vector256Maximum, + this.vector256Half, + this.vector256Scale, + out Vector256 c0, + out Vector256 c1, + out Vector256 c2, + out Vector256 c3); + + // Include all planar results in the returned vector so the JIT retains every calculation. + return c0 + c1 + c2 + c3; + } + + /// + /// Invokes the Vector512 RGB-to-JPEG operator. + /// + /// A checksum containing all converted component vectors. + [Benchmark] + [BenchmarkCategory("FromRgb")] + public Vector512 FromRgbVector512() + { + JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( + this.vector512C0, + this.vector512C1, + this.vector512C2, + this.vector512Maximum, + this.vector512Half, + this.vector512Scale, + out Vector512 c0, + out Vector512 c1, + out Vector512 c2, + out Vector512 c3); + + // Include all planar results in the returned vector so the JIT retains every calculation. + return c0 + c1 + c2 + c3; + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs new file mode 100644 index 000000000..bba07b1e1 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Compares the shared YCbCr operator traversal with the Vector512 implementation it replaces. +/// +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class YCbCrOperatorComparison +{ + private JpegColorConverterBase.YCbCrVector512 legacy; + private JpegColorConverterBase.JpegColorConverter operatorConverter; + private float[] legacyC0; + private float[] legacyC1; + private float[] legacyC2; + private float[] operatorC0; + private float[] operatorC1; + private float[] operatorC2; + private float[] r; + private float[] g; + private float[] b; + + /// + /// Gets or sets the number of pixels converted by each invocation. + /// + [Params(8, 128, 1024)] + public int Count { get; set; } + + /// + /// Creates equivalent converter inputs in independent component buffers. + /// + [GlobalSetup] + public void Setup() + { + this.legacy = new JpegColorConverterBase.YCbCrVector512(8); + this.operatorConverter = + new JpegColorConverterBase.JpegColorConverter(8); + + Random random = new(42); + this.legacyC0 = CreateRandomValues(this.Count, random); + this.legacyC1 = CreateRandomValues(this.Count, random); + this.legacyC2 = CreateRandomValues(this.Count, random); + this.operatorC0 = this.legacyC0.ToArray(); + this.operatorC1 = this.legacyC1.ToArray(); + this.operatorC2 = this.legacyC2.ToArray(); + this.r = CreateRandomValues(this.Count, random); + this.g = CreateRandomValues(this.Count, random); + this.b = CreateRandomValues(this.Count, random); + } + + /// + /// Converts YCbCr components to RGB using the Vector512 implementation. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("ToRgb")] + public void LegacyToRgb() + { + JpegColorConverterBase.ComponentValues values = + new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); + + this.legacy.ConvertToRgbInPlace(values); + } + + /// + /// Converts YCbCr components to RGB using the shared operator traversal. + /// + [Benchmark] + [BenchmarkCategory("ToRgb")] + public void OperatorToRgb() + { + JpegColorConverterBase.ComponentValues values = + new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); + + this.operatorConverter.ConvertToRgbInPlace(values); + } + + /// + /// Converts RGB to YCbCr components using the Vector512 implementation. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("FromRgb")] + public void LegacyFromRgb() + { + JpegColorConverterBase.ComponentValues values = + new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); + + this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); + } + + /// + /// Converts RGB to YCbCr components using the shared operator traversal. + /// + [Benchmark] + [BenchmarkCategory("FromRgb")] + public void OperatorFromRgb() + { + JpegColorConverterBase.ComponentValues values = + new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); + + this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); + } + + /// + /// Creates deterministic sample-domain values for one component plane. + /// + /// The number of samples to create. + /// The deterministic random source shared by setup. + /// The populated component plane. + private static float[] CreateRandomValues(int length, Random random) + { + float[] values = new float[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (float)random.NextDouble() * 255F; + } + + return values; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index ba0dce4c5..c11456014 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.ColorProfiles; @@ -43,6 +44,11 @@ public class JpegColorConverterTests Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } + /// + /// Verifies that each supported color space and precision resolves to an available converter. + /// + /// The JPEG color space. + /// The JPEG sample precision. [Theory] [InlineData(JpegColorSpace.Grayscale, 8)] [InlineData(JpegColorSpace.Grayscale, 12)] @@ -54,6 +60,10 @@ public class JpegColorConverterTests [InlineData(JpegColorSpace.RGB, 12)] [InlineData(JpegColorSpace.YCbCr, 8)] [InlineData(JpegColorSpace.YCbCr, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 12)] + [InlineData(JpegColorSpace.TiffYccK, 8)] + [InlineData(JpegColorSpace.TiffYccK, 12)] internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); @@ -74,19 +84,8 @@ public class JpegColorConverterTests static void RunTest(string arg) { // arrange - Type expectedType = typeof(JpegColorConverterBase.RgbScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector128); - } + Type expectedType = + typeof(JpegColorConverterBase.JpegColorConverter); // act JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); @@ -107,19 +106,8 @@ public class JpegColorConverterTests static void RunTest(string arg) { // arrange - Type expectedType = typeof(JpegColorConverterBase.GrayScaleScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector128); - } + Type expectedType = + typeof(JpegColorConverterBase.JpegColorConverter); // act JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); @@ -140,19 +128,8 @@ public class JpegColorConverterTests static void RunTest(string arg) { // arrange - Type expectedType = typeof(JpegColorConverterBase.CmykScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector128); - } + Type expectedType = + typeof(JpegColorConverterBase.JpegColorConverter); // act JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); @@ -173,19 +150,8 @@ public class JpegColorConverterTests static void RunTest(string arg) { // arrange - Type expectedType = typeof(JpegColorConverterBase.YCbCrScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector128); - } + Type expectedType = + typeof(JpegColorConverterBase.JpegColorConverter); // act JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); @@ -206,19 +172,8 @@ public class JpegColorConverterTests static void RunTest(string arg) { // arrange - Type expectedType = typeof(JpegColorConverterBase.YccKScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YccKVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YccKVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YccKVector128); - } + Type expectedType = + typeof(JpegColorConverterBase.JpegColorConverter); // act JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); @@ -229,6 +184,25 @@ public class JpegColorConverterTests } } + /// + /// Verifies that TIFF color spaces resolve to their closed shared converter types. + /// + /// The TIFF JPEG color space. + /// The expected closed converter type. + [Theory] + [InlineData( + JpegColorSpace.TiffCmyk, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.TiffYccK, + typeof(JpegColorConverterBase.JpegColorConverter))] + internal void GetConverterReturnsCorrectConverterWithTiffColorSpace(JpegColorSpace colorSpace, Type expectedType) + { + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); + + Assert.Equal(expectedType, converter.GetType()); + } + [Theory] [InlineData(JpegColorSpace.Grayscale, 1)] [InlineData(JpegColorSpace.Ycck, 4)] @@ -306,6 +280,171 @@ public class JpegColorConverterTests new JpegColorConverterBase.YCbCrScalar(8), precision: 2); + /// + /// Verifies YCbCr equivalence around every scalar and SIMD width boundary. + /// + /// The number of samples to convert. + /// The JPEG sample precision. + [Theory] + [InlineData(1, 8)] + [InlineData(3, 12)] + [InlineData(4, 8)] + [InlineData(7, 12)] + [InlineData(8, 8)] + [InlineData(15, 12)] + [InlineData(16, 8)] + [InlineData(31, 12)] + [InlineData(32, 8)] + [InlineData(40, 12)] + [InlineData(64, 8)] + [InlineData(128, 12)] + public void YCbCrOperatorMatchesScalarForAllVectorBoundaries(int length, int precision) + { + JpegColorConverterBase converter = + new JpegColorConverterBase.JpegColorConverter(precision); + JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); + + ValidateConversionToRgb(converter, baseline, length, 3, precision); + ValidateConversionFromRgb(converter, baseline, length, 3, precision); + } + + /// + /// Verifies that the YCbCr operator retains scalar behavior when hardware intrinsics are disabled. + /// + [Fact] + public void YCbCrOperatorMatchesScalarWithoutHardwareIntrinsics() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies converter equivalence around every scalar and SIMD width boundary. + /// + /// The color space under test. + /// The number of component planes written by the converter. + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1)] + [InlineData(JpegColorSpace.RGB, 3)] + [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.Ycck, 4)] + [InlineData(JpegColorSpace.TiffCmyk, 4)] + internal void OperatorsMatchScalarAcrossEveryWidthBoundary(JpegColorSpace colorSpace, int componentCount) + { + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); + JpegColorConverterBase baseline = colorSpace switch + { + JpegColorSpace.Grayscale => new JpegColorConverterBase.GrayScaleScalar(8), + JpegColorSpace.RGB => new JpegColorConverterBase.RgbScalar(8), + JpegColorSpace.Cmyk => new JpegColorConverterBase.CmykScalar(8), + JpegColorSpace.Ycck => new JpegColorConverterBase.YccKScalar(8), + JpegColorSpace.TiffCmyk => new JpegColorConverterBase.TiffCmykScalar(8), + _ => throw new InvalidOperationException(), + }; + + // These lengths exercise every point immediately below, at, and above the supported SIMD widths. + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; + + foreach (int length in lengths) + { + ValidateConversionToRgb(converter, baseline, length, componentCount, 8); + ValidateConversionFromRgb(converter, baseline, length, componentCount, 8); + } + } + + /// + /// Verifies TIFF YCCK decoding around every scalar and SIMD width boundary. + /// + /// The JPEG sample precision. + [Theory] + [InlineData(8)] + [InlineData(12)] + public void TiffYccKOperatorToRgbMatchesScalarAcrossEveryWidthBoundary(int precision) + { + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, precision); + JpegColorConverterBase baseline = new JpegColorConverterBase.TiffYccKScalar(precision); + + // The combined lengths force scalar, 128-bit, 256-bit, and 512-bit work on capable hardware. + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; + + foreach (int length in lengths) + { + ValidateConversionToRgb(converter, baseline, length, 4, precision); + } + } + + /// + /// Verifies TIFF YCCK encoding against the canonical normalized color-profile conversion. + /// + [Fact] + public void TiffYccKOperatorFromRgbMatchesColorProfileDefinition() + { + const int maximumLength = 40; + const float maximumValue = 255F; + const float halfValue = 128F; + const float tolerance = 0.0001F; + float[] rSeed = [0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 64, 192]; + float[] gSeed = [0, 255, 0, 255, 0, 127, 160, 16, 0, 255, 0, 255, 0, 127, 160, 16, 255, 0, 128, 96]; + float[] bSeed = [0, 255, 0, 0, 255, 127, 224, 80, 255, 0, 255, 0, 0, 127, 224, 80, 0, 255, 192, 32]; + float[] r = new float[maximumLength]; + float[] g = new float[maximumLength]; + float[] b = new float[maximumLength]; + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, 8); + + // Repeating the color set provides enough lanes to exercise every SIMD width and each mixed-width tail. + rSeed.CopyTo(r, 0); + rSeed.CopyTo(r, rSeed.Length); + gSeed.CopyTo(g, 0); + gSeed.CopyTo(g, gSeed.Length); + bSeed.CopyTo(b, 0); + bSeed.CopyTo(b, bSeed.Length); + + // The normalized color-profile implementation is the canonical definition; JPEG stores each result + // in the configured integer sample domain. + ColorProfileConverter reference = new(); + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40]; + + foreach (int length in lengths) + { + float[] y = new float[length]; + float[] cb = new float[length]; + float[] cr = new float[length]; + float[] k = new float[length]; + JpegColorConverterBase.ComponentValues values = new(4, y, cb, cr, k); + + converter.ConvertFromRgb(values, r.AsSpan(0, length), g.AsSpan(0, length), b.AsSpan(0, length)); + + for (int i = 0; i < length; i++) + { + Rgb rgb = new(r[i] / maximumValue, g[i] / maximumValue, b[i] / maximumValue); + YccK expected = reference.Convert(rgb); + + Assert.Equal(expected.Y * maximumValue, y[i], tolerance); + + // JPEG centers chroma on the integer sample midpoint (128 at 8-bit precision), whereas + // the color-profile definition centers normalized chroma exactly on 0.5. + Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], tolerance); + Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], tolerance); + Assert.Equal(expected.K * maximumValue, k[i], tolerance); + } + } + } + + /// + /// Runs the YCbCr equivalence check in the feature-test process. + /// + /// The unused feature-test argument. + private static void RunTest(string arg) + { + const int length = 40; + const int precision = 8; + JpegColorConverterBase converter = + new JpegColorConverterBase.JpegColorConverter(precision); + JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); + + ValidateConversionToRgb(converter, baseline, length, 3, precision); + ValidateConversionFromRgb(converter, baseline, length, 3, precision); + } + [Theory] [MemberData(nameof(Seeds))] public void FromCmykBasic(int seed) => @@ -736,6 +875,91 @@ public class JpegColorConverterTests } } + /// + /// Compares two component planes using an absolute floating-point tolerance. + /// + /// The expected component values. + /// The actual component values. + /// The maximum permitted absolute difference. + private static void CompareSequenceWithTolerance(Span expected, Span actual, float tolerance) + { + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i], tolerance); + } + } + + /// + /// Compares component-to-RGB conversion with a scalar reference implementation. + /// + /// The shared converter under test. + /// The scalar reference converter. + /// The number of samples to convert. + /// The number of source component planes. + /// The JPEG sample precision. + private static void ValidateConversionToRgb( + JpegColorConverterBase converter, + JpegColorConverterBase baseline, + int length, + int componentCount, + int precision) + { + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + + baseline.ConvertToRgbInPlace(expected); + converter.ConvertToRgbInPlace(actual); + + // SIMD multiply-add instructions can differ from the scalar expression by the final rounding bit. + CompareSequenceWithTolerance(expected.Component0, actual.Component0, 0.0001F); + CompareSequenceWithTolerance(expected.Component1, actual.Component1, 0.0001F); + CompareSequenceWithTolerance(expected.Component2, actual.Component2, 0.0001F); + } + + /// + /// Compares RGB-to-component conversion with a scalar reference implementation. + /// + /// The shared converter under test. + /// The scalar reference converter. + /// The number of samples to convert. + /// The number of destination component planes. + /// The JPEG sample precision. + private static void ValidateConversionFromRgb( + JpegColorConverterBase converter, + JpegColorConverterBase baseline, + int length, + int componentCount, + int precision) + { + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + Random random = new(precision); + float[] rLane = CreateRandomValues(length, random); + float[] gLane = CreateRandomValues(length, random); + float[] bLane = CreateRandomValues(length, random); + + baseline.ConvertFromRgb(expected, rLane, gLane, bLane); + converter.ConvertFromRgb(actual, rLane, gLane, bLane); + + // The generic traversal must preserve every plane owned by the closed color model. + CompareSequenceWithTolerance(expected.Component0, actual.Component0, 2); + + if (componentCount >= 2) + { + CompareSequenceWithTolerance(expected.Component1, actual.Component1, 2); + } + + if (componentCount >= 3) + { + CompareSequenceWithTolerance(expected.Component2, actual.Component2, 2); + } + + if (componentCount >= 4) + { + CompareSequenceWithTolerance(expected.Component3, actual.Component3, 2); + } + } + private static void Validate( JpegColorSpace colorSpace, in JpegColorConverterBase.ComponentValues original, From 873c64ec1244a34120d10a420c8cb9053000b8a5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 03:54:04 +1000 Subject: [PATCH 222/240] Normalize pixel blending SIMD traversal --- .../AssociatedAlphaPixelBlenders.Generated.cs | 87036 +-------------- .../AssociatedAlphaPixelBlenders.Generated.tt | 850 +- ...tedAlphaPixelBlender{TPixel,TOperator}.cs} | 16 +- .../DefaultPixelBlenders.Generated.cs | 87044 +--------------- .../DefaultPixelBlenders.Generated.tt | 858 +- .../PixelBlenders/IPixelBlenderOperator.cs | 46 + .../PixelBlender{TPixel,TOperator}.cs | 706 + .../PixelBlenderTraversalAssembly.cs | 327 + .../PixelFormats/PixelBlenderTests.cs | 104 +- 9 files changed, 8095 insertions(+), 168892 deletions(-) rename src/ImageSharp/PixelFormats/PixelBlenders/{AssociatedAlphaPixelBlender{TPixel}.cs => AssociatedAlphaPixelBlender{TPixel,TOperator}.cs} (67%) create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs create mode 100644 tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs index 94ae2ac71..85313dc20 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -3,84150 +3,3910 @@ // 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. +/// Provides the associated-alpha equations consumed by the shared pixel-blending traversal. /// -internal static partial class AssociatedAlphaPixelBlenders - where TPixel : unmanaged, IPixel +internal static class AssociatedAlphaPixelBlenderOperators { - /// - /// A pixel blender that implements the "NormalSrc" composition equation. + /// Applies the "NormalSrc" associated-alpha composition equation. /// - public sealed class NormalSrc : AssociatedAlphaPixelBlender + public readonly struct NormalSrc : IPixelBlenderOperator { - /// - /// 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))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); + } - 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); + /// + /// Applies the "MultiplySrc" associated-alpha composition equation. + /// + public readonly struct MultiplySrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); + } - if (Numerics.Modulo2(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); - } - } - } + /// + /// Applies the "AddSrc" associated-alpha composition equation. + /// + public readonly struct AddSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); + } - 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); + /// + /// Applies the "SubtractSrc" associated-alpha composition equation. + /// + public readonly struct SubtractSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); + } - 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); + /// + /// Applies the "ScreenSrc" associated-alpha composition equation. + /// + public readonly struct ScreenSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); - if (Numerics.Modulo2(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); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); + } + /// + /// Applies the "DarkenSrc" associated-alpha composition equation. + /// + public readonly struct DarkenSrc : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "LightenSrc" associated-alpha composition equation. + /// + public readonly struct LightenSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); + } - 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); + /// + /// Applies the "OverlaySrc" associated-alpha composition equation. + /// + public readonly struct OverlaySrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "HardLightSrc" associated-alpha composition equation. + /// + public readonly struct HardLightSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); + } + /// + /// Applies the "NormalSrcAtop" associated-alpha composition equation. + /// + public readonly struct NormalSrcAtop : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "MultiplySrcAtop" associated-alpha composition equation. + /// + public readonly struct MultiplySrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "AddSrcAtop" associated-alpha composition equation. + /// + public readonly struct AddSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "SubtractSrcAtop" associated-alpha composition equation. + /// + public readonly struct SubtractSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); + } + /// + /// Applies the "ScreenSrcAtop" associated-alpha composition equation. + /// + public readonly struct ScreenSrcAtop : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "DarkenSrcAtop" associated-alpha composition equation. + /// + public readonly struct DarkenSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "LightenSrcAtop" associated-alpha composition equation. + /// + public readonly struct LightenSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); + } - 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); - } + /// + /// Applies the "OverlaySrcAtop" associated-alpha composition equation. + /// + public readonly struct OverlaySrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, 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); + /// + /// Applies the "HardLightSrcAtop" associated-alpha composition equation. + /// + public readonly struct HardLightSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "NormalSrcOver" associated-alpha composition equation. + /// + public readonly struct NormalSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); + } - 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); + /// + /// Applies the "MultiplySrcOver" associated-alpha composition equation. + /// + public readonly struct MultiplySrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); + } - 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); + /// + /// Applies the "AddSrcOver" associated-alpha composition equation. + /// + public readonly struct AddSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); + } + /// + /// Applies the "SubtractSrcOver" associated-alpha composition equation. + /// + public readonly struct SubtractSrcOver : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "ScreenSrcOver" associated-alpha composition equation. + /// + public readonly struct ScreenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "DarkenSrcOver" associated-alpha composition equation. + /// + public readonly struct DarkenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); + } - 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); + /// + /// Applies the "LightenSrcOver" associated-alpha composition equation. + /// + public readonly struct LightenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "OverlaySrcOver" associated-alpha composition equation. + /// + public readonly struct OverlaySrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); + } + /// + /// Applies the "HardLightSrcOver" associated-alpha composition equation. + /// + public readonly struct HardLightSrcOver : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "NormalSrcIn" associated-alpha composition equation. + /// + public readonly struct NormalSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "MultiplySrcIn" associated-alpha composition equation. + /// + public readonly struct MultiplySrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "AddSrcIn" associated-alpha composition equation. + /// + public readonly struct AddSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "SubtractSrcIn" associated-alpha composition equation. + /// + public readonly struct SubtractSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); } /// - /// A pixel blender that implements the "MultiplySrc" composition equation. + /// Applies the "ScreenSrcIn" associated-alpha composition equation. /// - public sealed class MultiplySrc : AssociatedAlphaPixelBlender + public readonly struct ScreenSrcIn : IPixelBlenderOperator { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrc Instance { get; } = new MultiplySrc(); + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.MultiplySrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, 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); + /// + /// Applies the "DarkenSrcIn" associated-alpha composition equation. + /// + public readonly struct DarkenSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); + } - 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); + /// + /// Applies the "LightenSrcIn" associated-alpha composition equation. + /// + public readonly struct LightenSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); + } - if (Numerics.Modulo2(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); - } - } - } + /// + /// Applies the "OverlaySrcIn" associated-alpha composition equation. + /// + public readonly struct OverlaySrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); + } - 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); + /// + /// Applies the "HardLightSrcIn" associated-alpha composition equation. + /// + public readonly struct HardLightSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); + } - 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); + /// + /// Applies the "NormalSrcOut" associated-alpha composition equation. + /// + public readonly struct NormalSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); - if (Numerics.Modulo2(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); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); + } + /// + /// Applies the "MultiplySrcOut" associated-alpha composition equation. + /// + public readonly struct MultiplySrcOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); - - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, 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); + /// + /// Applies the "AddSrcOut" associated-alpha composition equation. + /// + public readonly struct AddSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "SubtractSrcOut" associated-alpha composition equation. + /// + public readonly struct SubtractSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); + } + /// + /// Applies the "ScreenSrcOut" associated-alpha composition equation. + /// + public readonly struct ScreenSrcOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "DarkenSrcOut" associated-alpha composition equation. + /// + public readonly struct DarkenSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); + } - 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); + /// + /// Applies the "LightenSrcOut" associated-alpha composition equation. + /// + public readonly struct LightenSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "OverlaySrcOut" associated-alpha composition equation. + /// + public readonly struct OverlaySrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); + } + /// + /// Applies the "HardLightSrcOut" associated-alpha composition equation. + /// + public readonly struct HardLightSrcOut : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); + } - 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); + /// + /// Applies the "NormalDest" associated-alpha composition equation. + /// + public readonly struct NormalDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); + } - 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); + /// + /// Applies the "MultiplyDest" associated-alpha composition equation. + /// + public readonly struct MultiplyDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); + } - 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); - } + /// + /// Applies the "AddDest" associated-alpha composition equation. + /// + public readonly struct AddDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, 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); + /// + /// Applies the "SubtractDest" associated-alpha composition equation. + /// + public readonly struct SubtractDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); + } - 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); + /// + /// Applies the "ScreenDest" associated-alpha composition equation. + /// + public readonly struct ScreenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); + } - 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); + /// + /// Applies the "DarkenDest" associated-alpha composition equation. + /// + public readonly struct DarkenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); + } - 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); + /// + /// Applies the "LightenDest" associated-alpha composition equation. + /// + public readonly struct LightenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); + } + /// + /// Applies the "OverlayDest" associated-alpha composition equation. + /// + public readonly struct OverlayDest : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "HardLightDest" associated-alpha composition equation. + /// + public readonly struct HardLightDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "NormalDestAtop" associated-alpha composition equation. + /// + public readonly struct NormalDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); + } - 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); + /// + /// Applies the "MultiplyDestAtop" associated-alpha composition equation. + /// + public readonly struct MultiplyDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "AddDestAtop" associated-alpha composition equation. + /// + public readonly struct AddDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); + } + /// + /// Applies the "SubtractDestAtop" associated-alpha composition equation. + /// + public readonly struct SubtractDestAtop : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "ScreenDestAtop" associated-alpha composition equation. + /// + public readonly struct ScreenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "DarkenDestAtop" associated-alpha composition equation. + /// + public readonly struct DarkenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "LightenDestAtop" associated-alpha composition equation. + /// + public readonly struct LightenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "OverlayDestAtop" associated-alpha composition equation. + /// + public readonly struct OverlayDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); } /// - /// A pixel blender that implements the "AddSrc" composition equation. + /// Applies the "HardLightDestAtop" associated-alpha composition equation. /// - public sealed class AddSrc : AssociatedAlphaPixelBlender + public readonly struct HardLightDestAtop : IPixelBlenderOperator { - /// - /// 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))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, 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)); - Vector512 opacity = Vector512.Create(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, 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); - } + /// + /// Applies the "NormalDestOver" associated-alpha composition equation. + /// + public readonly struct NormalDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, 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); - } + /// + /// Applies the "MultiplyDestOver" associated-alpha composition equation. + /// + public readonly struct MultiplyDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); - if (Numerics.Modulo2(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); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, 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); + /// + /// Applies the "AddDestOver" associated-alpha composition equation. + /// + public readonly struct AddDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); + } - 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); + /// + /// Applies the "SubtractDestOver" associated-alpha composition equation. + /// + public readonly struct SubtractDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); + } - if (Numerics.Modulo2(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); - } - } - } + /// + /// Applies the "ScreenDestOver" associated-alpha composition equation. + /// + public readonly struct ScreenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Applies the "DarkenDestOver" associated-alpha composition equation. + /// + public readonly struct DarkenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); + } - 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); - } + /// + /// Applies the "LightenDestOver" associated-alpha composition equation. + /// + public readonly struct LightenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Applies the "OverlayDestOver" associated-alpha composition equation. + /// + public readonly struct OverlayDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Applies the "HardLightDestOver" associated-alpha composition equation. + /// + public readonly struct HardLightDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, 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); + /// + /// Applies the "NormalDestIn" associated-alpha composition equation. + /// + public readonly struct NormalDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); + } - 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); - } + /// + /// Applies the "MultiplyDestIn" associated-alpha composition equation. + /// + public readonly struct MultiplyDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, 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); + /// + /// Applies the "AddDestIn" associated-alpha composition equation. + /// + public readonly struct AddDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Applies the "SubtractDestIn" associated-alpha composition equation. + /// + public readonly struct SubtractDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); /// - 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)); - } - } - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, 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); + /// + /// Applies the "ScreenDestIn" associated-alpha composition equation. + /// + public readonly struct ScreenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); + } - 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); + /// + /// Applies the "DarkenDestIn" associated-alpha composition equation. + /// + public readonly struct DarkenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); + } - 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); + /// + /// Applies the "LightenDestIn" associated-alpha composition equation. + /// + public readonly struct LightenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); + } - 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); + /// + /// Applies the "OverlayDestIn" associated-alpha composition equation. + /// + public readonly struct OverlayDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); + } + /// + /// Applies the "HardLightDestIn" associated-alpha composition equation. + /// + public readonly struct HardLightDestIn : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "NormalDestOut" associated-alpha composition equation. + /// + public readonly struct NormalDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "MultiplyDestOut" associated-alpha composition equation. + /// + public readonly struct MultiplyDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); + } - 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); + /// + /// Applies the "AddDestOut" associated-alpha composition equation. + /// + public readonly struct AddDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "SubtractDestOut" associated-alpha composition equation. + /// + public readonly struct SubtractDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); + } + /// + /// Applies the "ScreenDestOut" associated-alpha composition equation. + /// + public readonly struct ScreenDestOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "DarkenDestOut" associated-alpha composition equation. + /// + public readonly struct DarkenDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "LightenDestOut" associated-alpha composition equation. + /// + public readonly struct LightenDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "OverlayDestOut" associated-alpha composition equation. + /// + public readonly struct OverlayDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "HardLightDestOut" associated-alpha composition equation. + /// + public readonly struct HardLightDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); } /// - /// A pixel blender that implements the "SubtractSrc" composition equation. + /// Applies the "NormalClear" associated-alpha composition equation. /// - public sealed class SubtractSrc : AssociatedAlphaPixelBlender + public readonly struct NormalClear : IPixelBlenderOperator { - /// - /// 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))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, 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); - } + /// + /// Applies the "MultiplyClear" associated-alpha composition equation. + /// + public readonly struct MultiplyClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, 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); - } + /// + /// Applies the "AddClear" associated-alpha composition equation. + /// + public readonly struct AddClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); - if (Numerics.Modulo2(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); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, 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); + /// + /// Applies the "SubtractClear" associated-alpha composition equation. + /// + public readonly struct SubtractClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); + } - 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); + /// + /// Applies the "ScreenClear" associated-alpha composition equation. + /// + public readonly struct ScreenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); + } - if (Numerics.Modulo2(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); - } - } - } + /// + /// Applies the "DarkenClear" associated-alpha composition equation. + /// + public readonly struct DarkenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Applies the "LightenClear" associated-alpha composition equation. + /// + public readonly struct LightenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); + } - 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); - } + /// + /// Applies the "OverlayClear" associated-alpha composition equation. + /// + public readonly struct OverlayClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Applies the "HardLightClear" associated-alpha composition equation. + /// + public readonly struct HardLightClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Applies the "NormalXor" associated-alpha composition equation. + /// + public readonly struct NormalXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, 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); + /// + /// Applies the "MultiplyXor" associated-alpha composition equation. + /// + public readonly struct MultiplyXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); + } - 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); - } + /// + /// Applies the "AddXor" associated-alpha composition equation. + /// + public readonly struct AddXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, 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); + /// + /// Applies the "SubtractXor" associated-alpha composition equation. + /// + public readonly struct SubtractXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); - 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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Applies the "ScreenXor" associated-alpha composition equation. + /// + public readonly struct ScreenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); + } - 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); + /// + /// Applies the "DarkenXor" associated-alpha composition equation. + /// + public readonly struct DarkenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); + } - 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); - } + /// + /// Applies the "LightenXor" associated-alpha composition equation. + /// + public readonly struct LightenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); + } - 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); + /// + /// Applies the "OverlayXor" associated-alpha composition equation. + /// + public readonly struct OverlayXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); - 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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); - if (Numerics.Modulo2(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)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); + } + /// + /// Applies the "HardLightXor" associated-alpha composition equation. + /// + public readonly struct HardLightXor : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, 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 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); +/// +/// Provides associated-alpha pixel blenders for each color and alpha composition pair. +/// +/// The associated-alpha destination pixel format. +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Blends pixels with the "NormalSrc" composition equation. + /// + public sealed class NormalSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrc Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "MultiplySrc" composition equation. + /// + public sealed class MultiplySrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrc Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "AddSrc" composition equation. + /// + public sealed class AddSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrc Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "SubtractSrc" composition equation. + /// + public sealed class SubtractSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrc Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenSrc" composition equation. + /// + public sealed class ScreenSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrc Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenSrc" composition equation. + /// + public sealed class DarkenSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrc Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenSrc" composition equation. + /// + public sealed class LightenSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrc Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "OverlaySrc" composition equation. + /// + public sealed class OverlaySrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrc Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "HardLightSrc" composition equation. + /// + public sealed class HardLightSrc : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrc Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "NormalSrcAtop" composition equation. + /// + public sealed class NormalSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "MultiplySrcAtop" composition equation. + /// + public sealed class MultiplySrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcAtop Instance { get; } = new(); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Blends pixels with the "AddSrcAtop" composition equation. + /// + public sealed class AddSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcAtop Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "SubtractSrcAtop" composition equation. + /// + public sealed class SubtractSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "ScreenSrcAtop" composition equation. + /// + public sealed class ScreenSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcAtop Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "DarkenSrcAtop" composition equation. + /// + public sealed class DarkenSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "LightenSrcAtop" composition equation. + /// + public sealed class LightenSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcAtop Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "OverlaySrcAtop" composition equation. + /// + public sealed class OverlaySrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "HardLightSrcAtop" composition equation. + /// + public sealed class HardLightSrcAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "NormalSrcOver" composition equation. + /// + public sealed class NormalSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcOver Instance { get; } = new(); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Blends pixels with the "MultiplySrcOver" composition equation. + /// + public sealed class MultiplySrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcOver Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "AddSrcOver" composition equation. + /// + public sealed class AddSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcOver Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractSrcOver" composition equation. + /// + public sealed class SubtractSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcOver Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "ScreenSrcOver" composition equation. + /// + public sealed class ScreenSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcOver Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "DarkenSrcOver" composition equation. + /// + public sealed class DarkenSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcOver Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "LightenSrcOver" composition equation. + /// + public sealed class LightenSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcOver Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "OverlaySrcOver" composition equation. + /// + public sealed class OverlaySrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcOver Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "HardLightSrcOver" composition equation. + /// + public sealed class HardLightSrcOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcOver Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "NormalSrcIn" composition equation. + /// + public sealed class NormalSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcIn Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "MultiplySrcIn" composition equation. + /// + public sealed class MultiplySrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcIn Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "AddSrcIn" composition equation. + /// + public sealed class AddSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcIn Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractSrcIn" composition equation. + /// + public sealed class SubtractSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcIn Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "ScreenSrcIn" composition equation. + /// + public sealed class ScreenSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenSrcIn" composition equation. + /// + public sealed class DarkenSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcIn Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "LightenSrcIn" composition equation. + /// + public sealed class LightenSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlaySrcIn" composition equation. + /// + public sealed class OverlaySrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcIn Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "HardLightSrcIn" composition equation. + /// + public sealed class HardLightSrcIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcIn Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalSrcOut" composition equation. + /// + public sealed class NormalSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcOut Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "MultiplySrcOut" composition equation. + /// + public sealed class MultiplySrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcOut Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "AddSrcOut" composition equation. + /// + public sealed class AddSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcOut Instance { get; } = new(); } /// - /// A pixel blender that implements the "ScreenSrc" composition equation. + /// Blends pixels with the "SubtractSrcOut" composition equation. /// - public sealed class ScreenSrc : AssociatedAlphaPixelBlender + public sealed class SubtractSrcOut : + AssociatedAlphaPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); + public static SubtractSrcOut Instance { get; } = new(); + } - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.ScreenSrc(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + /// + /// Blends pixels with the "ScreenSrcOut" composition equation. + /// + public sealed class ScreenSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcOut Instance { get; } = new(); + } - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "DarkenSrcOut" composition equation. + /// + public sealed class DarkenSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcOut Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "LightenSrcOut" composition equation. + /// + public sealed class LightenSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlaySrcOut" composition equation. + /// + public sealed class OverlaySrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcOut Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "HardLightSrcOut" composition equation. + /// + public sealed class HardLightSrcOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "NormalDest" composition equation. + /// + public sealed class NormalDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDest Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "MultiplyDest" composition equation. + /// + public sealed class MultiplyDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDest Instance { get; } = new(); + } - 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)); - } - } - } + /// + /// Blends pixels with the "AddDest" composition equation. + /// + public sealed class AddDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDest Instance { get; } = new(); } /// - /// A pixel blender that implements the "OverlayXor" composition equation. + /// Blends pixels with the "SubtractDest" composition equation. /// - public sealed class OverlayXor : AssociatedAlphaPixelBlender + public sealed class SubtractDest : + AssociatedAlphaPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - 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)); - } - } - } + public static SubtractDest Instance { get; } = new(); } /// - /// A pixel blender that implements the "HardLightXor" composition equation. + /// Blends pixels with the "ScreenDest" composition equation. /// - public sealed class HardLightXor : AssociatedAlphaPixelBlender + public sealed class ScreenDest : + AssociatedAlphaPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - 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); + public static ScreenDest Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenDest" composition equation. + /// + public sealed class DarkenDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDest Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "LightenDest" composition equation. + /// + public sealed class LightenDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDest Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "OverlayDest" composition equation. + /// + public sealed class OverlayDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDest Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "HardLightDest" composition equation. + /// + public sealed class HardLightDest : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDest Instance { get; } = new(); + } - /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "NormalDestAtop" composition equation. + /// + public sealed class NormalDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestAtop Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "MultiplyDestAtop" composition equation. + /// + public sealed class MultiplyDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "AddDestAtop" composition equation. + /// + public sealed class AddDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "SubtractDestAtop" composition equation. + /// + public sealed class SubtractDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "ScreenDestAtop" composition equation. + /// + public sealed class ScreenDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestAtop Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "DarkenDestAtop" composition equation. + /// + public sealed class DarkenDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenDestAtop" composition equation. + /// + public sealed class LightenDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlayDestAtop" composition equation. + /// + public sealed class OverlayDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "HardLightDestAtop" composition equation. + /// + public sealed class HardLightDestAtop : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestAtop Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "NormalDestOver" composition equation. + /// + public sealed class NormalDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestOver Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "MultiplyDestOver" composition equation. + /// + public sealed class MultiplyDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestOver Instance { get; } = new(); + } - /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "AddDestOver" composition equation. + /// + public sealed class AddDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestOver Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "SubtractDestOver" composition equation. + /// + public sealed class SubtractDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestOver Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenDestOver" composition equation. + /// + public sealed class ScreenDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestOver Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "DarkenDestOver" composition equation. + /// + public sealed class DarkenDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestOver Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenDestOver" composition equation. + /// + public sealed class LightenDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestOver Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "OverlayDestOver" composition equation. + /// + public sealed class OverlayDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestOver Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "HardLightDestOver" composition equation. + /// + public sealed class HardLightDestOver : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestOver Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "NormalDestIn" composition equation. + /// + public sealed class NormalDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestIn Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "MultiplyDestIn" composition equation. + /// + public sealed class MultiplyDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "AddDestIn" composition equation. + /// + public sealed class AddDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "SubtractDestIn" composition equation. + /// + public sealed class SubtractDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestIn Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "ScreenDestIn" composition equation. + /// + public sealed class ScreenDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestIn Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "DarkenDestIn" composition equation. + /// + public sealed class DarkenDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestIn Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "LightenDestIn" composition equation. + /// + public sealed class LightenDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlayDestIn" composition equation. + /// + public sealed class OverlayDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestIn Instance { get; } = new(); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Blends pixels with the "HardLightDestIn" composition equation. + /// + public sealed class HardLightDestIn : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestIn Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "NormalDestOut" composition equation. + /// + public sealed class NormalDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestOut Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "MultiplyDestOut" composition equation. + /// + public sealed class MultiplyDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestOut Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "AddDestOut" composition equation. + /// + public sealed class AddDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestOut Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractDestOut" composition equation. + /// + public sealed class SubtractDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestOut Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "ScreenDestOut" composition equation. + /// + public sealed class ScreenDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenDestOut" composition equation. + /// + public sealed class DarkenDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenDestOut" composition equation. + /// + public sealed class LightenDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestOut Instance { get; } = new(); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Blends pixels with the "OverlayDestOut" composition equation. + /// + public sealed class OverlayDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestOut Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "HardLightDestOut" composition equation. + /// + public sealed class HardLightDestOut : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestOut Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalClear" composition equation. + /// + public sealed class NormalClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalClear Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "MultiplyClear" composition equation. + /// + public sealed class MultiplyClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyClear Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "AddClear" composition equation. + /// + public sealed class AddClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddClear Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "SubtractClear" composition equation. + /// + public sealed class SubtractClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractClear Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenClear" composition equation. + /// + public sealed class ScreenClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenClear Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "DarkenClear" composition equation. + /// + public sealed class DarkenClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenClear Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "LightenClear" composition equation. + /// + public sealed class LightenClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenClear Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "OverlayClear" composition equation. + /// + public sealed class OverlayClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayClear Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "HardLightClear" composition equation. + /// + public sealed class HardLightClear : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightClear Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalXor" composition equation. + /// + public sealed class NormalXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalXor Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "MultiplyXor" composition equation. + /// + public sealed class MultiplyXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyXor Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "AddXor" composition equation. + /// + public sealed class AddXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddXor Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "SubtractXor" composition equation. + /// + public sealed class SubtractXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractXor Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "ScreenXor" composition equation. + /// + public sealed class ScreenXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenXor Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "DarkenXor" composition equation. + /// + public sealed class DarkenXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenXor Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "LightenXor" composition equation. + /// + public sealed class LightenXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenXor Instance { get; } = new(); + } - 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); - } + /// + /// Blends pixels with the "OverlayXor" composition equation. + /// + public sealed class OverlayXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayXor Instance { get; } = new(); + } - if (Numerics.Modulo2(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)); - } - } - } + /// + /// Blends pixels with the "HardLightXor" composition equation. + /// + public sealed class HardLightXor : + AssociatedAlphaPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightXor Instance { get; } = new(); } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt index 69426b729..70607feb1 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt @@ -4,31 +4,19 @@ #> <#@ 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 []{ +var composers = new [] +{ "Src", "SrcAtop", "SrcOver", @@ -43,7 +31,8 @@ var composers = new []{ "Xor", }; -var blenders = new []{ +var blenders = new [] +{ "Normal", "Multiply", "Add", @@ -52,796 +41,79 @@ var blenders = new []{ "Darken", "Lighten", "Overlay", - "HardLight" + "HardLight", }; - - foreach(var composer in composers) { - foreach(var blender in blenders) { - - var blender_composer= $"{blender}{composer}"; +#> +/// +/// Provides the associated-alpha equations consumed by the shared pixel-blending traversal. +/// +internal static class AssociatedAlphaPixelBlenderOperators +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; #> /// - /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// Applies the "<#= blenderComposer #>" associated-alpha composition equation. /// - public sealed class <#= blender_composer#> : AssociatedAlphaPixelBlender + public readonly struct <#= blenderComposer #> : IPixelBlenderOperator { - /// - /// 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))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - 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); - } - } - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, 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); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); + } - 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)); - } - } - } +/// +/// Provides associated-alpha pixel blenders for each color and alpha composition pair. +/// +/// The associated-alpha destination pixel format. +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; +#> + /// + /// Blends pixels with the "<#= blenderComposer #>" composition equation. + /// + public sealed class <#= blenderComposer #> : + AssociatedAlphaPixelBlender> + { + /// + /// Gets the shared blender instance. + /// + public static <#= blenderComposer #> Instance { get; } = new(); } <# } } - #> } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs similarity index 67% rename from src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs rename to src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs index 9c71e97e5..00427308c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs @@ -9,11 +9,25 @@ 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 +/// The Porter-Duff equation. +internal abstract class AssociatedAlphaPixelBlender : PixelBlender where TPixel : unmanaged, IPixel + where TOperator : struct, IPixelBlenderOperator { private static readonly PixelOperations Operations = PixelOperations.Instance; + /// + public sealed override TPixel Blend(TPixel background, TPixel source, float amount) + { + // Associated RGB and alpha must remain in their stored representation throughout composition. + Vector4 result = TOperator.Invoke( + background.ToAssociatedScaledVector4(), + source.ToAssociatedScaledVector4(), + Numerics.Clamp(amount, 0, 1F)); + + return TPixel.FromAssociatedScaledVector4(result); + } + /// protected override void ToBlendVector4( Configuration configuration, diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 983508d2d..250d789dd 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -3,84158 +3,3910 @@ // 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; /// -/// Collection of Porter Duff alpha blending functions applying different composition models. +/// Provides the straight-alpha equations consumed by the shared pixel-blending traversal. /// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel +internal static class DefaultPixelBlenderOperators { - /// - /// A pixel blender that implements the "NormalSrc" composition equation. + /// Applies the "NormalSrc" straight-alpha composition equation. /// - public class NormalSrc : PixelBlender + public readonly struct NormalSrc : IPixelBlenderOperator { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "MultiplySrc" straight-alpha composition equation. + /// + public readonly struct MultiplySrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - } + /// + /// Applies the "AddSrc" straight-alpha composition equation. + /// + public readonly struct AddSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrc(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddSrc(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddSrc(background, source, amount); + } - 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); + /// + /// Applies the "SubtractSrc" straight-alpha composition equation. + /// + public readonly struct SubtractSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); + } - 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); + /// + /// Applies the "ScreenSrc" straight-alpha composition equation. + /// + public readonly struct ScreenSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); + } + /// + /// Applies the "DarkenSrc" straight-alpha composition equation. + /// + public readonly struct DarkenSrc : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "LightenSrc" straight-alpha composition equation. + /// + public readonly struct LightenSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "OverlaySrc" straight-alpha composition equation. + /// + public readonly struct OverlaySrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "HardLightSrc" straight-alpha composition equation. + /// + public readonly struct HardLightSrc : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); + } + /// + /// Applies the "NormalSrcAtop" straight-alpha composition equation. + /// + public readonly struct NormalSrcAtop : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "MultiplySrcAtop" straight-alpha composition equation. + /// + public readonly struct MultiplySrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "AddSrcAtop" straight-alpha composition equation. + /// + public readonly struct AddSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcAtop(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddSrcAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddSrcAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "SubtractSrcAtop" straight-alpha composition equation. + /// + public readonly struct SubtractSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); + } + /// + /// Applies the "ScreenSrcAtop" straight-alpha composition equation. + /// + public readonly struct ScreenSrcAtop : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "DarkenSrcAtop" straight-alpha composition equation. + /// + public readonly struct DarkenSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); - Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Applies the "LightenSrcAtop" straight-alpha composition equation. + /// + public readonly struct LightenSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); + } - Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Applies the "OverlaySrcAtop" straight-alpha composition equation. + /// + public readonly struct OverlaySrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, 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); + /// + /// Applies the "HardLightSrcAtop" straight-alpha composition equation. + /// + public readonly struct HardLightSrcAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); + } - 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); + /// + /// Applies the "NormalSrcOver" straight-alpha composition equation. + /// + public readonly struct NormalSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); - Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Applies the "MultiplySrcOver" straight-alpha composition equation. + /// + public readonly struct MultiplySrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); + } - 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); + /// + /// Applies the "AddSrcOver" straight-alpha composition equation. + /// + public readonly struct AddSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); - Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); + } + /// + /// Applies the "SubtractSrcOver" straight-alpha composition equation. + /// + public readonly struct SubtractSrcOver : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "ScreenSrcOver" straight-alpha composition equation. + /// + public readonly struct ScreenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "DarkenSrcOver" straight-alpha composition equation. + /// + public readonly struct DarkenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); - Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); + } - 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); + /// + /// Applies the "LightenSrcOver" straight-alpha composition equation. + /// + public readonly struct LightenSrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "OverlaySrcOver" straight-alpha composition equation. + /// + public readonly struct OverlaySrcOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); - Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); + } + /// + /// Applies the "HardLightSrcOver" straight-alpha composition equation. + /// + public readonly struct HardLightSrcOver : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "NormalSrcIn" straight-alpha composition equation. + /// + public readonly struct NormalSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "MultiplySrcIn" straight-alpha composition equation. + /// + public readonly struct MultiplySrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); - Vector512 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "AddSrcIn" straight-alpha composition equation. + /// + public readonly struct AddSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcIn(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddSrcIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddSrcIn(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "SubtractSrcIn" straight-alpha composition equation. + /// + public readonly struct SubtractSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); - Vector256 blended = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); } /// - /// A pixel blender that implements the "MultiplySrc" composition equation. + /// Applies the "ScreenSrcIn" straight-alpha composition equation. /// - public class MultiplySrc : PixelBlender + public readonly struct ScreenSrcIn : IPixelBlenderOperator { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrc Instance { get; } = new MultiplySrc(); + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, 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); + /// + /// Applies the "DarkenSrcIn" straight-alpha composition equation. + /// + public readonly struct DarkenSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "LightenSrcIn" straight-alpha composition equation. + /// + public readonly struct LightenSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - } + /// + /// Applies the "OverlaySrcIn" straight-alpha composition equation. + /// + public readonly struct OverlaySrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, amount); + } - 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); + /// + /// Applies the "HardLightSrcIn" straight-alpha composition equation. + /// + public readonly struct HardLightSrcIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); + } - 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); + /// + /// Applies the "NormalSrcOut" straight-alpha composition equation. + /// + public readonly struct NormalSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); + } + /// + /// Applies the "MultiplySrcOut" straight-alpha composition equation. + /// + public readonly struct MultiplySrcOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); - - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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); + /// + /// Applies the "AddSrcOut" straight-alpha composition equation. + /// + public readonly struct AddSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "SubtractSrcOut" straight-alpha composition equation. + /// + public readonly struct SubtractSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); + } + /// + /// Applies the "ScreenSrcOut" straight-alpha composition equation. + /// + public readonly struct ScreenSrcOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "DarkenSrcOut" straight-alpha composition equation. + /// + public readonly struct DarkenSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "LightenSrcOut" straight-alpha composition equation. + /// + public readonly struct LightenSrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcOut(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenSrcOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenSrcOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Applies the "OverlaySrcOut" straight-alpha composition equation. + /// + public readonly struct OverlaySrcOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); + } + /// + /// Applies the "HardLightSrcOut" straight-alpha composition equation. + /// + public readonly struct HardLightSrcOut : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, amount); + } - 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); + /// + /// Applies the "NormalDest" straight-alpha composition equation. + /// + public readonly struct NormalDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDest(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalDest(background, source, amount); - Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalDest(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Applies the "MultiplyDest" straight-alpha composition equation. + /// + public readonly struct MultiplyDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); + } - Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Applies the "AddDest" straight-alpha composition equation. + /// + public readonly struct AddDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDest(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddDest(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddDest(background, source, 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); + /// + /// Applies the "SubtractDest" straight-alpha composition equation. + /// + public readonly struct SubtractDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDest(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractDest(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractDest(background, source, amount); + } - 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); + /// + /// Applies the "ScreenDest" straight-alpha composition equation. + /// + public readonly struct ScreenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); - Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Applies the "DarkenDest" straight-alpha composition equation. + /// + public readonly struct DarkenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); + } - 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); + /// + /// Applies the "LightenDest" straight-alpha composition equation. + /// + public readonly struct LightenDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDest(background, source, amount); - Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenDest(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenDest(background, source, amount); + } + /// + /// Applies the "OverlayDest" straight-alpha composition equation. + /// + public readonly struct OverlayDest : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "HardLightDest" straight-alpha composition equation. + /// + public readonly struct HardLightDest : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "NormalDestAtop" straight-alpha composition equation. + /// + public readonly struct NormalDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); - Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); + } - 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); + /// + /// Applies the "MultiplyDestAtop" straight-alpha composition equation. + /// + public readonly struct MultiplyDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "AddDestAtop" straight-alpha composition equation. + /// + public readonly struct AddDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); - Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); + } + /// + /// Applies the "SubtractDestAtop" straight-alpha composition equation. + /// + public readonly struct SubtractDestAtop : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "ScreenDestAtop" straight-alpha composition equation. + /// + public readonly struct ScreenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "DarkenDestAtop" straight-alpha composition equation. + /// + public readonly struct DarkenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); - Vector512 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "LightenDestAtop" straight-alpha composition equation. + /// + public readonly struct LightenDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestAtop(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenDestAtop(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenDestAtop(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "OverlayDestAtop" straight-alpha composition equation. + /// + public readonly struct OverlayDestAtop : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); - Vector256 blended = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); } /// - /// A pixel blender that implements the "AddSrc" composition equation. + /// Applies the "HardLightDestAtop" straight-alpha composition equation. /// - public class AddSrc : PixelBlender + public readonly struct HardLightDestAtop : IPixelBlenderOperator { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, 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)); - Vector512 opacity = Vector512.Create(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "NormalDestOver" straight-alpha composition equation. + /// + public readonly struct NormalDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "MultiplyDestOver" straight-alpha composition equation. + /// + public readonly struct MultiplyDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, 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); + /// + /// Applies the "AddDestOver" straight-alpha composition equation. + /// + public readonly struct AddDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "SubtractDestOver" straight-alpha composition equation. + /// + public readonly struct SubtractDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount); - } - } - } + /// + /// Applies the "ScreenDestOver" straight-alpha composition equation. + /// + public readonly struct ScreenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestOver(background, 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenDestOver(background, source, amount); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Applies the "DarkenDestOver" straight-alpha composition equation. + /// + public readonly struct DarkenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); + } - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "LightenDestOver" straight-alpha composition equation. + /// + public readonly struct LightenDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Applies the "OverlayDestOver" straight-alpha composition equation. + /// + public readonly struct OverlayDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + /// Applies the "HardLightDestOver" straight-alpha composition equation. + /// + public readonly struct HardLightDestOver : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestOver(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightDestOver(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightDestOver(background, source, 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); + /// + /// Applies the "NormalDestIn" straight-alpha composition equation. + /// + public readonly struct NormalDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); + } - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "MultiplyDestIn" straight-alpha composition equation. + /// + public readonly struct MultiplyDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, 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); + /// + /// Applies the "AddDestIn" straight-alpha composition equation. + /// + public readonly struct AddDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + /// Applies the "SubtractDestIn" straight-alpha composition equation. + /// + public readonly struct SubtractDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestIn(background, source, amount); /// - 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 = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractDestIn(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractDestIn(background, source, 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); + /// + /// Applies the "ScreenDestIn" straight-alpha composition equation. + /// + public readonly struct ScreenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestIn(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenDestIn(background, source, 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 opacity = Vector512.Create(amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenDestIn(background, source, amount); + } - 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); + /// + /// Applies the "DarkenDestIn" straight-alpha composition equation. + /// + public readonly struct DarkenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); - Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Applies the "LightenDestIn" straight-alpha composition equation. + /// + public readonly struct LightenDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); + } - 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); + /// + /// Applies the "OverlayDestIn" straight-alpha composition equation. + /// + public readonly struct OverlayDestIn : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); - Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); + } + /// + /// Applies the "HardLightDestIn" straight-alpha composition equation. + /// + public readonly struct HardLightDestIn : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); - Vector512 vOne = Vector512.Create(1F); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "NormalDestOut" straight-alpha composition equation. + /// + public readonly struct NormalDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "MultiplyDestOut" straight-alpha composition equation. + /// + public readonly struct MultiplyDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); - Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); + } - 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); + /// + /// Applies the "AddDestOut" straight-alpha composition equation. + /// + public readonly struct AddDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); - Vector256 vOne = Vector256.Create(1F); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "SubtractDestOut" straight-alpha composition equation. + /// + public readonly struct SubtractDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); - Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); + } + /// + /// Applies the "ScreenDestOut" straight-alpha composition equation. + /// + public readonly struct ScreenDestOut : IPixelBlenderOperator + { /// - 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); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestOut(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenDestOut(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenDestOut(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Applies the "DarkenDestOut" straight-alpha composition equation. + /// + public readonly struct DarkenDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Applies the "LightenDestOut" straight-alpha composition equation. + /// + public readonly struct LightenDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); - Vector512 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Applies the "OverlayDestOut" straight-alpha composition equation. + /// + public readonly struct OverlayDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestOut(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayDestOut(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayDestOut(background, source, amount); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Applies the "HardLightDestOut" straight-alpha composition equation. + /// + public readonly struct HardLightDestOut : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); - Vector256 blended = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); } /// - /// A pixel blender that implements the "SubtractSrc" composition equation. + /// Applies the "NormalClear" straight-alpha composition equation. /// - public class SubtractSrc : PixelBlender + public readonly struct NormalClear : IPixelBlenderOperator { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalClear(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalClear(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "MultiplyClear" straight-alpha composition equation. + /// + public readonly struct MultiplyClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "AddClear" straight-alpha composition equation. + /// + public readonly struct AddClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddClear(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddClear(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddClear(background, source, 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); + /// + /// Applies the "SubtractClear" straight-alpha composition equation. + /// + public readonly struct SubtractClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Applies the "ScreenClear" straight-alpha composition equation. + /// + public readonly struct ScreenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount); - } - } - } + /// + /// Applies the "DarkenClear" straight-alpha composition equation. + /// + public readonly struct DarkenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenClear(background, 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenClear(background, source, amount); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Applies the "LightenClear" straight-alpha composition equation. + /// + public readonly struct LightenClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenClear(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenClear(background, source, amount); + } - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "OverlayClear" straight-alpha composition equation. + /// + public readonly struct OverlayClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Applies the "HardLightClear" straight-alpha composition equation. + /// + public readonly struct HardLightClear : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + /// Applies the "NormalXor" straight-alpha composition equation. + /// + public readonly struct NormalXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalXor(background, source, amount); /// - 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); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.NormalXor(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.NormalXor(background, source, 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); + /// + /// Applies the "MultiplyXor" straight-alpha composition equation. + /// + public readonly struct MultiplyXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); + } - destinationBase = PorterDuffFunctions.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); - } + /// + /// Applies the "AddXor" straight-alpha composition equation. + /// + public readonly struct AddXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddXor(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.AddXor(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.AddXor(background, source, 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); + /// + /// Applies the "SubtractXor" straight-alpha composition equation. + /// + public readonly struct SubtractXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); - destinationBase = PorterDuffFunctions.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); - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + /// Applies the "ScreenXor" straight-alpha composition equation. + /// + public readonly struct ScreenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenXor(background, source, amount); /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.ScreenXor(background, source, 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.ScreenXor(background, source, amount); + } - 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); + /// + /// Applies the "DarkenXor" straight-alpha composition equation. + /// + public readonly struct DarkenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); - 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); + } - Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Applies the "LightenXor" straight-alpha composition equation. + /// + public readonly struct LightenXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenXor(background, source, amount); - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.LightenXor(background, source, amount); - 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); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.LightenXor(background, source, amount); + } - 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); + /// + /// Applies the "OverlayXor" straight-alpha composition equation. + /// + public readonly struct OverlayXor : IPixelBlenderOperator + { + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); - Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); + } + /// + /// Applies the "HardLightXor" straight-alpha composition equation. + /// + public readonly struct HardLightXor : IPixelBlenderOperator + { /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightXor(background, source, 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); + /// + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.HardLightXor(background, source, amount); - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.HardLightXor(background, source, 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 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); +/// +/// Provides straight-alpha pixel blenders for each color and alpha composition pair. +/// +/// The destination pixel format. +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Blends pixels with the "NormalSrc" composition equation. + /// + public sealed class NormalSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrc Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "MultiplySrc" composition equation. + /// + public sealed class MultiplySrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrc Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "AddSrc" composition equation. + /// + public sealed class AddSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrc Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "SubtractSrc" composition equation. + /// + public sealed class SubtractSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrc Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenSrc" composition equation. + /// + public sealed class ScreenSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrc Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenSrc" composition equation. + /// + public sealed class DarkenSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrc Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenSrc" composition equation. + /// + public sealed class LightenSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrc Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "OverlaySrc" composition equation. + /// + public sealed class OverlaySrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrc Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "HardLightSrc" composition equation. + /// + public sealed class HardLightSrc : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrc Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "NormalSrcAtop" composition equation. + /// + public sealed class NormalSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "MultiplySrcAtop" composition equation. + /// + public sealed class MultiplySrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcAtop Instance { get; } = new(); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Blends pixels with the "AddSrcAtop" composition equation. + /// + public sealed class AddSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcAtop Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "SubtractSrcAtop" composition equation. + /// + public sealed class SubtractSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "ScreenSrcAtop" composition equation. + /// + public sealed class ScreenSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcAtop Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "DarkenSrcAtop" composition equation. + /// + public sealed class DarkenSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "LightenSrcAtop" composition equation. + /// + public sealed class LightenSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcAtop Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "OverlaySrcAtop" composition equation. + /// + public sealed class OverlaySrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcAtop Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "HardLightSrcAtop" composition equation. + /// + public sealed class HardLightSrcAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "NormalSrcOver" composition equation. + /// + public sealed class NormalSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcOver Instance { get; } = new(); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Blends pixels with the "MultiplySrcOver" composition equation. + /// + public sealed class MultiplySrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcOver Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "AddSrcOver" composition equation. + /// + public sealed class AddSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcOver Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractSrcOver" composition equation. + /// + public sealed class SubtractSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcOver Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "ScreenSrcOver" composition equation. + /// + public sealed class ScreenSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcOver Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "DarkenSrcOver" composition equation. + /// + public sealed class DarkenSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcOver Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "LightenSrcOver" composition equation. + /// + public sealed class LightenSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcOver Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "OverlaySrcOver" composition equation. + /// + public sealed class OverlaySrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcOver Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "HardLightSrcOver" composition equation. + /// + public sealed class HardLightSrcOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcOver Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "NormalSrcIn" composition equation. + /// + public sealed class NormalSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcIn Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "MultiplySrcIn" composition equation. + /// + public sealed class MultiplySrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcIn Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "AddSrcIn" composition equation. + /// + public sealed class AddSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcIn Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractSrcIn" composition equation. + /// + public sealed class SubtractSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractSrcIn Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "ScreenSrcIn" composition equation. + /// + public sealed class ScreenSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcIn Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "DarkenSrcIn" composition equation. + /// + public sealed class DarkenSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcIn Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "LightenSrcIn" composition equation. + /// + public sealed class LightenSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlaySrcIn" composition equation. + /// + public sealed class OverlaySrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcIn Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "HardLightSrcIn" composition equation. + /// + public sealed class HardLightSrcIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcIn Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalSrcOut" composition equation. + /// + public sealed class NormalSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalSrcOut Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "MultiplySrcOut" composition equation. + /// + public sealed class MultiplySrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplySrcOut Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "AddSrcOut" composition equation. + /// + public sealed class AddSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddSrcOut Instance { get; } = new(); } /// - /// A pixel blender that implements the "ScreenSrc" composition equation. + /// Blends pixels with the "SubtractSrcOut" composition equation. /// - public class ScreenSrc : PixelBlender + public sealed class SubtractSrcOut : + DefaultPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); + public static SubtractSrcOut Instance { get; } = new(); + } - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + /// + /// Blends pixels with the "ScreenSrcOut" composition equation. + /// + public sealed class ScreenSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenSrcOut Instance { get; } = new(); + } - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "DarkenSrcOut" composition equation. + /// + public sealed class DarkenSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenSrcOut Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "LightenSrcOut" composition equation. + /// + public sealed class LightenSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenSrcOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlaySrcOut" composition equation. + /// + public sealed class OverlaySrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlaySrcOut Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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); - } + /// + /// Blends pixels with the "HardLightSrcOut" composition equation. + /// + public sealed class HardLightSrcOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightSrcOut Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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); + /// + /// Blends pixels with the "NormalDest" composition equation. + /// + public sealed class NormalDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDest Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "MultiplyDest" composition equation. + /// + public sealed class MultiplyDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDest Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrc" composition equation. - /// - public class DarkenSrc : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrc" composition equation. - /// - public class LightenSrc : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrc" composition equation. - /// - public class OverlaySrc : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrc" composition equation. - /// - public class HardLightSrc : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrc(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcAtop" composition equation. - /// - public class NormalSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcAtop" composition equation. - /// - public class MultiplySrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcAtop" composition equation. - /// - public class AddSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcAtop" composition equation. - /// - public class SubtractSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. - /// - public class DarkenSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. - /// - public class OverlaySrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. - /// - public class NormalSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. - /// - public class MultiplySrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOver" composition equation. - /// - public class AddSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. - /// - public class SubtractSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. - /// - public class ScreenSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. - /// - public class DarkenSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. - /// - public class LightenSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. - /// - public class HardLightSrcOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. - /// - public class MultiplySrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. - /// - public class SubtractSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. - /// - public class DarkenSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. - /// - public class OverlaySrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. - /// - public class NormalSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOut" composition equation. - /// - public class AddSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. - /// - public class ScreenSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. - /// - public class LightenSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlaySrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. - /// - public class HardLightSrcOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightSrcOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDest" composition equation. - /// - public class MultiplyDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDest" composition equation. - /// - public class SubtractDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDest" composition equation. - /// - public class DarkenDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDest" composition equation. - /// - public class OverlayDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDest(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDest(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. - /// - public class NormalDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestAtop" composition equation. - /// - public class AddDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. - /// - public class ScreenDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. - /// - public class LightenDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. - /// - public class HardLightDestAtop : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestAtop(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. - /// - public class MultiplyDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. - /// - public class SubtractDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOver" composition equation. - /// - public class ScreenDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. - /// - public class DarkenDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOver" composition equation. - /// - public class LightenDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. - /// - public class OverlayDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOver" composition equation. - /// - public class HardLightDestOver : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOver(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestIn" composition equation. - /// - public class NormalDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestIn" composition equation. - /// - public class MultiplyDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestIn" composition equation. - /// - public class AddDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestIn" composition equation. - /// - public class SubtractDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. - /// - public class ScreenDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestIn" composition equation. - /// - public class DarkenDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestIn" composition equation. - /// - public class LightenDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestIn" composition equation. - /// - public class OverlayDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. - /// - public class HardLightDestIn : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestIn(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOut" composition equation. - /// - public class NormalDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. - /// - public class MultiplyDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOut" composition equation. - /// - public class AddDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. - /// - public class SubtractDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOut" composition equation. - /// - public class ScreenDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. - /// - public class DarkenDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOut" composition equation. - /// - public class LightenDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. - /// - public class OverlayDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOut" composition equation. - /// - public class HardLightDestOut : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightDestOut(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalClear" composition equation. - /// - public class NormalClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyClear" composition equation. - /// - public class MultiplyClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddClear" composition equation. - /// - public class AddClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractClear" composition equation. - /// - public class SubtractClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenClear" composition equation. - /// - public class ScreenClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenClear" composition equation. - /// - public class LightenClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayClear" composition equation. - /// - public class OverlayClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightClear" composition equation. - /// - public class HardLightClear : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightClear(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightClear(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightClear(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalXor" composition equation. - /// - public class NormalXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.NormalXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.NormalXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyXor" composition equation. - /// - public class MultiplyXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.MultiplyXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddXor" composition equation. - /// - public class AddXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.AddXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.AddXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractXor" composition equation. - /// - public class SubtractXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.SubtractXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.SubtractXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenXor" composition equation. - /// - public class ScreenXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.ScreenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.ScreenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenXor" composition equation. - /// - public class DarkenXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.DarkenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.DarkenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenXor" composition equation. - /// - public class LightenXor : PixelBlender - { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.LightenXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.LightenXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "AddDest" composition equation. + /// + public sealed class AddDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDest Instance { get; } = new(); } /// - /// A pixel blender that implements the "OverlayXor" composition equation. + /// Blends pixels with the "SubtractDest" composition equation. /// - public class OverlayXor : PixelBlender + public sealed class SubtractDest : + DefaultPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - public static OverlayXor Instance { get; } = new OverlayXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.OverlayXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.OverlayXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + public static SubtractDest Instance { get; } = new(); } /// - /// A pixel blender that implements the "HardLightXor" composition equation. + /// Blends pixels with the "ScreenDest" composition equation. /// - public class HardLightXor : PixelBlender + public sealed class ScreenDest : + DefaultPixelBlender { /// - /// Gets the static instance of this blender. + /// Gets the shared blender instance. /// - public static HardLightXor Instance { get; } = new HardLightXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.HardLightXor(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), 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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightXor(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.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 = PorterDuffFunctions.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] = PorterDuffFunctions.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); + public static ScreenDest Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "DarkenDest" composition equation. + /// + public sealed class DarkenDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDest Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "LightenDest" composition equation. + /// + public sealed class LightenDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDest Instance { get; } = new(); + } - destinationBase = PorterDuffFunctions.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); - } + /// + /// Blends pixels with the "OverlayDest" composition equation. + /// + public sealed class OverlayDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDest Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "HardLightDest" composition equation. + /// + public sealed class HardLightDest : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDest Instance { get; } = new(); + } - /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "NormalDestAtop" composition equation. + /// + public sealed class NormalDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestAtop Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "MultiplyDestAtop" composition equation. + /// + public sealed class MultiplyDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "AddDestAtop" composition equation. + /// + public sealed class AddDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "SubtractDestAtop" composition equation. + /// + public sealed class SubtractDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestAtop Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "ScreenDestAtop" composition equation. + /// + public sealed class ScreenDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestAtop Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "DarkenDestAtop" composition equation. + /// + public sealed class DarkenDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestAtop Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "LightenDestAtop" composition equation. + /// + public sealed class LightenDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlayDestAtop" composition equation. + /// + public sealed class OverlayDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestAtop Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "HardLightDestAtop" composition equation. + /// + public sealed class HardLightDestAtop : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestAtop Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "NormalDestOver" composition equation. + /// + public sealed class NormalDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestOver Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "MultiplyDestOver" composition equation. + /// + public sealed class MultiplyDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestOver Instance { get; } = new(); + } - /// - protected override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) - { - amount = Numerics.Clamp(amount, 0, 1); + /// + /// Blends pixels with the "AddDestOver" composition equation. + /// + public sealed class AddDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestOver Instance { get; } = new(); + } - if (Avx512F.IsSupported && destination.Length >= 4) - { - // Divide by 4 as 4 elements per Vector4 and 16 per Vector512 - ref Vector512 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector512 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u); + /// + /// Blends pixels with the "SubtractDestOver" composition equation. + /// + public sealed class SubtractDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestOver Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenDestOver" composition equation. + /// + public sealed class ScreenDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestOver Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "DarkenDestOver" composition equation. + /// + public sealed class DarkenDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestOver Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenDestOver" composition equation. + /// + public sealed class LightenDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestOver Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "OverlayDestOver" composition equation. + /// + public sealed class OverlayDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestOver Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "HardLightDestOver" composition equation. + /// + public sealed class HardLightDestOver : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestOver Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "NormalDestIn" composition equation. + /// + public sealed class NormalDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestIn Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "MultiplyDestIn" composition equation. + /// + public sealed class MultiplyDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "AddDestIn" composition equation. + /// + public sealed class AddDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "SubtractDestIn" composition equation. + /// + public sealed class SubtractDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestIn Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "ScreenDestIn" composition equation. + /// + public sealed class ScreenDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestIn Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "DarkenDestIn" composition equation. + /// + public sealed class DarkenDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestIn Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "LightenDestIn" composition equation. + /// + public sealed class LightenDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestIn Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "OverlayDestIn" composition equation. + /// + public sealed class OverlayDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestIn Instance { get; } = new(); + } - Vector512 vOne = Vector512.Create(1F); + /// + /// Blends pixels with the "HardLightDestIn" composition equation. + /// + public sealed class HardLightDestIn : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestIn Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "NormalDestOut" composition equation. + /// + public sealed class NormalDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalDestOut Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "MultiplyDestOut" composition equation. + /// + public sealed class MultiplyDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyDestOut Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "AddDestOut" composition equation. + /// + public sealed class AddDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddDestOut Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "SubtractDestOut" composition equation. + /// + public sealed class SubtractDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractDestOut Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "ScreenDestOut" composition equation. + /// + public sealed class ScreenDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenDestOut Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "DarkenDestOut" composition equation. + /// + public sealed class DarkenDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenDestOut Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "LightenDestOut" composition equation. + /// + public sealed class LightenDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenDestOut Instance { get; } = new(); + } - Vector256 vOne = Vector256.Create(1F); + /// + /// Blends pixels with the "OverlayDestOut" composition equation. + /// + public sealed class OverlayDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayDestOut Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "HardLightDestOut" composition equation. + /// + public sealed class HardLightDestOut : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightDestOut Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalClear" composition equation. + /// + public sealed class NormalClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalClear Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "MultiplyClear" composition equation. + /// + public sealed class MultiplyClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyClear Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "AddClear" composition equation. + /// + public sealed class AddClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddClear Instance { get; } = new(); + } - /// - 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); + /// + /// Blends pixels with the "SubtractClear" composition equation. + /// + public sealed class SubtractClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractClear Instance { get; } = new(); + } - ref Vector512 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "ScreenClear" composition equation. + /// + public sealed class ScreenClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenClear Instance { get; } = new(); + } - Vector512 sourceBase = Vector512.Create( - source.X, source.Y, source.Z, source.W, - source.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); + /// + /// Blends pixels with the "DarkenClear" composition equation. + /// + public sealed class DarkenClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenClear Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - float amount0 = amountBase; - float amount1 = Unsafe.Add(ref amountBase, 1); - float amount2 = Unsafe.Add(ref amountBase, 2); - float amount3 = Unsafe.Add(ref amountBase, 3); + /// + /// Blends pixels with the "LightenClear" composition equation. + /// + public sealed class LightenClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenClear Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four amount values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 opacity = Vector512.Create( - amount0, amount0, amount0, amount0, - amount1, amount1, amount1, amount1, - amount2, amount2, amount2, amount2, - amount3, amount3, amount3, amount3); - opacity = Vector512.Min(Vector512.Max(Vector512.Zero, opacity), vOne); + /// + /// Blends pixels with the "OverlayClear" composition equation. + /// + public sealed class OverlayClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayClear Instance { get; } = new(); + } - float coverage0 = coverageBase; - float coverage1 = Unsafe.Add(ref coverageBase, 1); - float coverage2 = Unsafe.Add(ref coverageBase, 2); - float coverage3 = Unsafe.Add(ref coverageBase, 3); + /// + /// Blends pixels with the "HardLightClear" composition equation. + /// + public sealed class HardLightClear : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightClear Instance { get; } = new(); + } - // We need to create a Vector512 containing the current four coverage values - // taking up each quarter of the Vector512 and then clamp them. - Vector512 coverageVector = Vector512.Create( - coverage0, coverage0, coverage0, coverage0, - coverage1, coverage1, coverage1, coverage1, - coverage2, coverage2, coverage2, coverage2, - coverage3, coverage3, coverage3, coverage3); - coverageVector = Vector512.Min(Vector512.Max(Vector512.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "NormalXor" composition equation. + /// + public sealed class NormalXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static NormalXor Instance { get; } = new(); + } - Vector512 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } + /// + /// Blends pixels with the "MultiplyXor" composition equation. + /// + public sealed class MultiplyXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static MultiplyXor Instance { get; } = new(); + } - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + /// + /// Blends pixels with the "AddXor" composition equation. + /// + public sealed class AddXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static AddXor Instance { get; } = new(); + } - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - ref float coverageBase = ref MemoryMarshal.GetReference(coverage); + /// + /// Blends pixels with the "SubtractXor" composition equation. + /// + public sealed class SubtractXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static SubtractXor Instance { get; } = new(); + } - 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); + /// + /// Blends pixels with the "ScreenXor" composition equation. + /// + public sealed class ScreenXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static ScreenXor Instance { get; } = new(); + } - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); + /// + /// Blends pixels with the "DarkenXor" composition equation. + /// + public sealed class DarkenXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static DarkenXor Instance { get; } = new(); + } - // We need to create a Vector256 containing the current and next coverage values - // taking up each half of the Vector256 and then clamp them. - Vector256 coverageVector = Vector256.Create( - Vector128.Create(coverageBase), - Vector128.Create(Unsafe.Add(ref coverageBase, 1))); - coverageVector = Avx.Min(Avx.Max(Vector256.Zero, coverageVector), vOne); + /// + /// Blends pixels with the "LightenXor" composition equation. + /// + public sealed class LightenXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static LightenXor Instance { get; } = new(); + } - Vector256 blended = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } + /// + /// Blends pixels with the "OverlayXor" composition equation. + /// + public sealed class OverlayXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static OverlayXor Instance { get; } = new(); + } - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.HardLightXor(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } + /// + /// Blends pixels with the "HardLightXor" composition equation. + /// + public sealed class HardLightXor : + DefaultPixelBlender + { + /// + /// Gets the shared blender instance. + /// + public static HardLightXor Instance { get; } = new(); } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index b1d8134ce..99610b7d8 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -4,39 +4,19 @@ #> <#@ 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; -/// -/// Collection of Porter Duff alpha blending functions applying different composition models. -/// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel -{ - <# -var composers = new []{ +var composers = new [] +{ "Src", "SrcAtop", "SrcOver", @@ -51,7 +31,8 @@ var composers = new []{ "Xor", }; -var blenders = new []{ +var blenders = new [] +{ "Normal", "Multiply", "Add", @@ -60,796 +41,79 @@ var blenders = new []{ "Darken", "Lighten", "Overlay", - "HardLight" + "HardLight", }; - - foreach(var composer in composers) { - foreach(var blender in blenders) { - - var blender_composer= $"{blender}{composer}"; +#> +/// +/// Provides the straight-alpha equations consumed by the shared pixel-blending traversal. +/// +internal static class DefaultPixelBlenderOperators +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; #> /// - /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// Applies the "<#= blenderComposer #>" straight-alpha composition equation. /// - public class <#= blender_composer#> : PixelBlender + public readonly struct <#= blenderComposer #> : IPixelBlenderOperator { - /// - /// 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.FromUnassociatedScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - } + public static Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.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 = PorterDuffFunctions.<#=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] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - - /// - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - - /// - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - - /// - 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 = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 4); - coverageBase = ref Unsafe.Add(ref coverageBase, 4); - } - - int remainder = Numerics.Modulo4(destination.Length); - if (remainder != 0) - { - for (int i = destination.Length - remainder; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } - else if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - 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); + public static Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); + } - Vector256 blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - coverageBase = ref Unsafe.Add(ref coverageBase, 2); - } +<# + } +} +#> +} - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); - destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); - } - } - } +/// +/// Provides straight-alpha pixel blenders for each color and alpha composition pair. +/// +/// The destination pixel format. +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; +#> + /// + /// Blends pixels with the "<#= blenderComposer #>" composition equation. + /// + public sealed class <#= blenderComposer #> : + DefaultPixelBlender> + { + /// + /// Gets the shared blender instance. + /// + public static <#= blenderComposer #> Instance { get; } = new(); } <# } } - #> } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs new file mode 100644 index 000000000..bdb43e7be --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Defines a Porter-Duff equation that can be applied by the shared pixel-blending traversal. +/// +internal interface IPixelBlenderOperator +{ + /// + /// Blends one pixel represented by four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity in the range 0 through 1. + /// The blended RGBA lanes. + static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); + + /// + /// Blends two pixels represented by two consecutive groups of four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity repeated across each pixel's four lanes. + /// The blended RGBA lanes. + static abstract Vector256 Invoke( + Vector256 background, + Vector256 source, + Vector256 amount); + + /// + /// Blends four pixels represented by four consecutive groups of four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity repeated across each pixel's four lanes. + /// The blended RGBA lanes. + static abstract Vector512 Invoke( + Vector512 background, + Vector512 source, + Vector512 amount); +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs new file mode 100644 index 000000000..f50151256 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs @@ -0,0 +1,706 @@ +// 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; + +/// +/// Applies a statically selected Porter-Duff equation across pixel-vector rows. +/// +/// The destination pixel format. +/// The Porter-Duff equation. +internal abstract class PixelBlender : PixelBlender + where TPixel : unmanaged, IPixel + where TOperator : struct, IPixelBlenderOperator +{ + /// + protected sealed override void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + { + // Public entry points validate the row lengths, so all three references can advance in lockstep. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + // A 512-bit register holds four complete RGBA pixels in [R,G,B,A] groups. + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + Unsafe.Add(ref sourceBase, i), + amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // A 256-bit register holds two complete RGBA pixels in [R,G,B,A] groups. + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + Unsafe.Add(ref sourceBase, i), + amountVector); + } + + scalarStart = vectorCount * 2; + } + + // Vector4 is both the scalar pixel representation and the portable SIMD fallback. + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( + Unsafe.Add(ref backgroundRef, (uint)i), + Unsafe.Add(ref sourceRef, (uint)i), + amount); + } + } + + /// + protected sealed override void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount) + { + // Public entry points validate the row lengths, so the destination and background advance together. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + // Repeat one [R,G,B,A] source group four times to match the four background pixels. + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + sourceVector, + amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + // Repeat one [R,G,B,A] source group twice to match the two background pixels. + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + sourceVector, + amountVector); + } + + scalarStart = vectorCount * 2; + } + + // The remaining pixel count is at most three after AVX-512 or one after AVX2. + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( + Unsafe.Add(ref backgroundRef, (uint)i), + source, + amount); + } + } + + /// + protected sealed override void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + { + // Each amount belongs to one pixel and must be repeated across that pixel's four RGBA lanes. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + // Four scalar amounts become four contiguous [a,a,a,a] groups before clamping. + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + Unsafe.Add(ref sourceBase, i), + amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + // Two scalar amounts become two contiguous [a,a,a,a] groups before clamping. + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + Unsafe.Add(ref sourceBase, i), + amountVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( + Unsafe.Add(ref backgroundRef, (uint)i), + Unsafe.Add(ref sourceRef, (uint)i), + Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount) + { + // The source is invariant, while each background pixel has its own independently clamped amount. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + sourceVector, + amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( + Unsafe.Add(ref backgroundBase, i), + sourceVector, + amountVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( + Unsafe.Add(ref backgroundRef, (uint)i), + source, + Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + ReadOnlySpan coverage) + { + // Coverage mixes the composed result back toward the original background, so it is fused into this pass. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke( + backgroundVector, + Unsafe.Add(ref sourceBase, i), + amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke( + backgroundVector, + Unsafe.Add(ref sourceBase, i), + amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke( + backgroundPixel, + Unsafe.Add(ref sourceRef, (uint)i), + amount); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( + backgroundPixel, + blended, + Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount, + ReadOnlySpan coverage) + { + // The constant source is expanded once per selected width and reused for the complete row. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke(backgroundPixel, source, amount); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( + backgroundPixel, + blended, + Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + { + // Amount controls composition while coverage controls the final mix with the untouched background. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke( + backgroundVector, + Unsafe.Add(ref sourceBase, i), + amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke( + backgroundVector, + Unsafe.Add(ref sourceBase, i), + amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke( + backgroundPixel, + Unsafe.Add(ref sourceRef, (uint)i), + Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( + backgroundPixel, + blended, + Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendWithCoverageFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + { + // The invariant source is expanded once; only amount and coverage are gathered for each vector batch. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Avx512F.IsSupported && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Avx2.IsSupported && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( + backgroundVector, + blended, + coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke( + backgroundPixel, + source, + Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( + backgroundPixel, + blended, + Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + } + } + + /// + /// Repeats one RGBA pixel twice to fill a 256-bit register. + /// + /// The pixel represented by ordered RGBA lanes. + /// Two consecutive copies of the pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 CreateVector256(Vector4 pixel) + => Vector256.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); + + /// + /// Repeats one RGBA pixel four times to fill a 512-bit register. + /// + /// The pixel represented by ordered RGBA lanes. + /// Four consecutive copies of the pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateVector512(Vector4 pixel) + => Vector512.Create( + pixel.X, + pixel.Y, + pixel.Z, + pixel.W, + pixel.X, + pixel.Y, + pixel.Z, + pixel.W, + pixel.X, + pixel.Y, + pixel.Z, + pixel.W, + pixel.X, + pixel.Y, + pixel.Z, + pixel.W); + + /// + /// Expands and clamps two per-pixel scalar values for two packed RGBA pixels. + /// + /// The first of two contiguous scalar values. + /// Two consecutive groups containing one clamped value in each group's four lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 CreateClampedVector256(ref float values) + { + Vector256 result = Vector256.Create( + Vector128.Create(values), + Vector128.Create(Unsafe.Add(ref values, 1))); + + // Amount and coverage share the same public 0..1 contract and therefore the same packed clamp. + return Avx.Min(Avx.Max(Vector256.Zero, result), Vector256.Create(1F)); + } + + /// + /// Expands and clamps four per-pixel scalar values for four packed RGBA pixels. + /// + /// The first of four contiguous scalar values. + /// Four consecutive groups containing one clamped value in each group's four lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateClampedVector512(ref float values) + { + Vector512 result = Vector512.Create( + values, + values, + values, + values, + Unsafe.Add(ref values, 1), + Unsafe.Add(ref values, 1), + Unsafe.Add(ref values, 1), + Unsafe.Add(ref values, 1), + Unsafe.Add(ref values, 2), + Unsafe.Add(ref values, 2), + Unsafe.Add(ref values, 2), + Unsafe.Add(ref values, 2), + Unsafe.Add(ref values, 3), + Unsafe.Add(ref values, 3), + Unsafe.Add(ref values, 3), + Unsafe.Add(ref values, 3)); + + return Vector512.Min(Vector512.Max(Vector512.Zero, result), Vector512.Create(1F)); + } +} + +/// +/// Applies a statically selected Porter-Duff equation to straight-alpha pixels. +/// +/// The straight-alpha pixel format. +/// The Porter-Duff equation. +internal abstract class DefaultPixelBlender : PixelBlender + where TPixel : unmanaged, IPixel + where TOperator : struct, IPixelBlenderOperator +{ + /// + public sealed override TPixel Blend(TPixel background, TPixel source, float amount) + { + // The operator consumes the same unassociated, scaled representation used by the bulk conversion path. + Vector4 result = TOperator.Invoke( + background.ToUnassociatedScaledVector4(), + source.ToUnassociatedScaledVector4(), + Numerics.Clamp(amount, 0, 1F)); + + return TPixel.FromUnassociatedScaledVector4(result); + } +} diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs new file mode 100644 index 000000000..a8d6095c7 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs @@ -0,0 +1,327 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; + +/// +/// Exposes every shared pixel-blender traversal shape for assembly inspection. +/// +/// +/// Seven pixels leave three scalar pixels after AVX-512 or one scalar pixel after AVX2, so each +/// hardware job contains both its widest supported loop and the portable Vector4 remainder. +/// +[Config(typeof(Config.Analysis))] +public class PixelBlenderTraversalAssembly +{ + private const int Count = 7; + private const float Amount = .625F; + + private readonly ExposedNormalSrcOverBlender blender = new(); + private readonly Vector4[] destination = new Vector4[Count]; + private readonly Vector4[] background = new Vector4[Count]; + private readonly Vector4[] source = new Vector4[Count]; + private readonly float[] amounts = new float[Count]; + private readonly float[] coverage = new float[Count]; + private Vector4 constantSource; + + /// + /// Populates all lanes with deterministic, non-constant values. + /// + [GlobalSetup] + public void Setup() + { + Random random = new(42); + + for (int i = 0; i < Count; i++) + { + // Distinct RGBA lanes make incorrect pixel grouping visible in both results and assembly. + this.background[i] = CreatePixel(random); + this.source[i] = CreatePixel(random); + this.amounts[i] = random.NextSingle(); + this.coverage[i] = random.NextSingle(); + } + + this.constantSource = CreatePixel(random); + } + + /// + /// Blends a source row with one shared amount. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 SourceSpanScalarAmount() + { + this.blender.BlendSourceSpanScalarAmount( + this.destination, + this.background, + this.source, + Amount); + + return this.destination[^1]; + } + + /// + /// Blends a constant source with one shared amount. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 ConstantSourceScalarAmount() + { + this.blender.BlendConstantSourceScalarAmount( + this.destination, + this.background, + this.constantSource, + Amount); + + return this.destination[^1]; + } + + /// + /// Blends a source row with per-pixel amounts. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 SourceSpanAmountSpan() + { + this.blender.BlendSourceSpanAmountSpan( + this.destination, + this.background, + this.source, + this.amounts); + + return this.destination[^1]; + } + + /// + /// Blends a constant source with per-pixel amounts. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 ConstantSourceAmountSpan() + { + this.blender.BlendConstantSourceAmountSpan( + this.destination, + this.background, + this.constantSource, + this.amounts); + + return this.destination[^1]; + } + + /// + /// Blends a source row with one shared amount and per-pixel coverage. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 SourceSpanScalarAmountCoverage() + { + this.blender.BlendSourceSpanScalarAmountCoverage( + this.destination, + this.background, + this.source, + Amount, + this.coverage); + + return this.destination[^1]; + } + + /// + /// Blends a constant source with one shared amount and per-pixel coverage. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 ConstantSourceScalarAmountCoverage() + { + this.blender.BlendConstantSourceScalarAmountCoverage( + this.destination, + this.background, + this.constantSource, + Amount, + this.coverage); + + return this.destination[^1]; + } + + /// + /// Blends a source row with per-pixel amounts and coverage. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 SourceSpanAmountSpanCoverage() + { + this.blender.BlendSourceSpanAmountSpanCoverage( + this.destination, + this.background, + this.source, + this.amounts, + this.coverage); + + return this.destination[^1]; + } + + /// + /// Blends a constant source with per-pixel amounts and coverage. + /// + /// The final destination pixel. + [Benchmark] + public Vector4 ConstantSourceAmountSpanCoverage() + { + this.blender.BlendConstantSourceAmountSpanCoverage( + this.destination, + this.background, + this.constantSource, + this.amounts, + this.coverage); + + return this.destination[^1]; + } + + /// + /// Creates one non-constant RGBA sample. + /// + /// The deterministic value source. + /// The sample pixel. + private static Vector4 CreatePixel(Random random) + => new(random.NextSingle(), random.NextSingle(), random.NextSingle(), random.NextSingle()); + + /// + /// Exposes the protected shared traversal overloads without adding benchmark hooks to production APIs. + /// + private sealed class ExposedNormalSrcOverBlender : + DefaultPixelBlender + { + /// + /// Invokes the source-span, scalar-amount traversal. + /// + /// The destination vectors. + /// The background vectors. + /// The source vectors. + /// The shared source amount. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendSourceSpanScalarAmount( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + => this.BlendFunction(destination, background, source, amount); + + /// + /// Invokes the constant-source, scalar-amount traversal. + /// + /// The destination vectors. + /// The background vectors. + /// The constant source vector. + /// The shared source amount. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendConstantSourceScalarAmount( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount) + => this.BlendFunction(destination, background, source, amount); + + /// + /// Invokes the source-span, amount-span traversal. + /// + /// The destination vectors. + /// The background vectors. + /// The source vectors. + /// The per-pixel source amounts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendSourceSpanAmountSpan( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + => this.BlendFunction(destination, background, source, amount); + + /// + /// Invokes the constant-source, amount-span traversal. + /// + /// The destination vectors. + /// The background vectors. + /// The constant source vector. + /// The per-pixel source amounts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendConstantSourceAmountSpan( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount) + => this.BlendFunction(destination, background, source, amount); + + /// + /// Invokes the source-span, scalar-amount traversal with coverage. + /// + /// The destination vectors. + /// The background vectors. + /// The source vectors. + /// The shared source amount. + /// The per-pixel coverage values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendSourceSpanScalarAmountCoverage( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + ReadOnlySpan coverage) + => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); + + /// + /// Invokes the constant-source, scalar-amount traversal with coverage. + /// + /// The destination vectors. + /// The background vectors. + /// The constant source vector. + /// The shared source amount. + /// The per-pixel coverage values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendConstantSourceScalarAmountCoverage( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount, + ReadOnlySpan coverage) + => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); + + /// + /// Invokes the source-span, amount-span traversal with coverage. + /// + /// The destination vectors. + /// The background vectors. + /// The source vectors. + /// The per-pixel source amounts. + /// The per-pixel coverage values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendSourceSpanAmountSpanCoverage( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); + + /// + /// Invokes the constant-source, amount-span traversal with coverage. + /// + /// The destination vectors. + /// The background vectors. + /// The constant source vector. + /// The per-pixel source amounts. + /// The per-pixel coverage values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BlendConstantSourceAmountSpanCoverage( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount, + ReadOnlySpan coverage) + => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index a851e2fcf..448de9ec4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -791,42 +791,104 @@ public class PixelBlenderTests ExerciseBlender(blender, background, source); } + /// + /// Exercises every bulk overload across vector-width boundaries and compares it with single-pixel composition. + /// + /// The pixel format. + /// The blender under test. + /// The background pixel. + /// The source pixel. 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]; + const float amount = .625F; - TPixel expected = blender.Blend(background, source, 1F); + float[] amounts = [0F, .125F, .375F, .625F, .875F, 1F, .5F]; + float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F]; - TPixel[] destination = new TPixel[4]; - TPixel[] backgroundSpan = [background, background, background, background]; - TPixel[] sourceSpan = [source, source, source, source]; + // Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail. + TPixel[] destination = new TPixel[7]; + TPixel[] expected = new TPixel[7]; + TPixel[] backgroundSpan = [background, background, background, background, background, background, background]; + TPixel[] sourceSpan = [source, source, source, 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); - Assert.All(destination, x => Assert.Equal(expected, x)); - - blender.Blend(Configuration.Default, destination, backgroundSpan, source, 1F, constantSourceBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + Array.Fill(expected, blender.Blend(background, source, amount)); - blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer); + Assert.Equal(expected, destination); blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + Assert.Equal(expected, destination); - blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, coverage, sourceSpanBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(background, source, amounts[i]); + } - blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, 1F, coverage, constantSourceBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, sourceSpanBuffer); + Assert.Equal(expected, destination); - blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + blender.Blend(Configuration.Default, destination, backgroundSpan, source, amounts, constantSourceBuffer); + Assert.Equal(expected, destination); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, background, source, amount, coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer); - Assert.All(destination, x => Assert.Equal(expected, x)); + Assert.Equal(expected, destination); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, background, source, amounts[i], coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amounts, coverage, constantSourceBuffer); + Assert.Equal(expected, destination); + } + + /// + /// Computes a one-pixel coverage result through the scalar remainder path. + /// + /// The pixel format. + /// The blender under test. + /// The background pixel. + /// The source pixel. + /// The source opacity. + /// The pixel coverage. + /// The blended pixel. + private static TPixel BlendWithCoverageScalar( + PixelBlender blender, + TPixel background, + TPixel source, + float amount, + float coverage) + where TPixel : unmanaged, IPixel + { + Span destination = stackalloc TPixel[1]; + Span backgroundSpan = stackalloc TPixel[1] { background }; + Span sourceSpan = stackalloc TPixel[1] { source }; + Span coverageSpan = stackalloc float[1] { coverage }; + Span buffer = stackalloc Vector4[3]; + + blender.BlendWithCoverage( + Configuration.Default, + destination, + backgroundSpan, + sourceSpan, + amount, + coverageSpan, + buffer); + + return destination[0]; } } From 5a2fbbe795074969b3710dcad6462f6dacfe3651 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 07:38:24 +1000 Subject: [PATCH 223/240] Normalize packed pixel conversion shuffles --- .../Helpers/Shuffle/IComponentShuffle.cs | 32 +- .../Common/Helpers/Shuffle/IPad3Shuffle4.cs | 131 +++--- .../Common/Helpers/Shuffle/IShuffle3.cs | 56 +-- .../Common/Helpers/Shuffle/IShuffle4.cs | 395 ++++++++++++------ .../Common/Helpers/Shuffle/IShuffle4Slice3.cs | 130 +++--- .../Common/Helpers/SimdUtils.Shuffle.cs | 366 ++++++++++++++-- .../PixelFormats/Utils/PixelConverter.cs | 60 +-- .../Bulk/Pad3Shuffle4Channel.cs | 5 +- .../Bulk/Shuffle3Channel.cs | 3 +- .../Bulk/Shuffle4Slice3Channel.cs | 5 +- .../Bulk/ShuffleByte4Channel.cs | 2 +- .../PixelConversion/PackedPixelConversion.cs | 222 ++++++++++ .../PackedPixelConversionAssembly.cs | 128 ++++++ .../Common/SimdUtilsTests.Shuffle.cs | 100 +---- 14 files changed, 1157 insertions(+), 478 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs create mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index c856267db..6b80845ed 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -1,36 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) -// and ROTR (Rotate Right) emitting efficient CPU instructions: -// https://github.com/dotnet/coreclr/pull/1830 +using System.Runtime.Intrinsics; + namespace SixLabors.ImageSharp; /// -/// Defines the contract for methods that allow the shuffling of pixel components. -/// Used for shuffling on platforms that do not support Hardware Intrinsics. +/// Defines a stateless operation over packed pixel components. /// internal interface IComponentShuffle { /// - /// Shuffles then slices 8-bit integers in - /// using a byte control and store the results in . - /// If successful, this method will reduce the length of length - /// by the shuffle amount. + /// Reorders one packed pixel. /// - /// The source span of bytes. - /// The destination span of bytes. - void ShuffleReduce(ref ReadOnlySpan source, ref Span destination); + /// The source components, with the first component in the least-significant byte. + /// The reordered packed components. + static abstract uint Invoke(uint source); /// - /// Shuffle 8-bit integers in - /// using the control and store the results in . + /// Reorders the packed pixels in a 128-bit vector. /// - /// The source span of bytes. - /// The destination span of bytes. - /// - /// Implementation can assume that source.Length is less or equal than destination.Length. - /// Loops should iterate using source.Length. - /// - void Shuffle(ReadOnlySpan source, Span destination); + /// The source pixels. + /// The reordered pixels. + static abstract Vector128 Invoke(Vector128 source); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs index bbe14b099..d9d8e35fc 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs @@ -1,99 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; +using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; -/// +/// +/// Defines a stateless operation that reorders a three-component pixel after adding opaque alpha. +/// internal interface IPad3Shuffle4 : IComponentShuffle { } -internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : IPad3Shuffle4 +/// +/// Preserves XYZ order and appends opaque W. +/// +internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 { - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); + public static uint Invoke(uint source) => source; - for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4) - { - // Expanding 3-byte pixels to 4 bytes can overwrite the next source - // triplet when spans overlap. Assemble the padded pixel first, then - // shuffle from the staged uint. - uint packed = - Unsafe.Add(ref sBase, i + 0u) | - ((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | - ((uint)Unsafe.Add(ref sBase, i + 2u) << 16) | - 0xFF000000; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) => source; +} - ref byte pBase = ref Unsafe.As(ref packed); +/// +/// Reorders padded XYZW components to WXYZ. +/// +internal readonly struct WXYZPad3Shuffle4 : IPad3Shuffle4 +{ + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Invoke(uint source) => BitOperations.RotateLeft(source, 8); - Unsafe.Add(ref dBase, j + 0u) = Unsafe.Add(ref pBase, p0); - Unsafe.Add(ref dBase, j + 1u) = Unsafe.Add(ref pBase, p1); - Unsafe.Add(ref dBase, j + 2u) = Unsafe.Add(ref pBase, p2); - Unsafe.Add(ref dBase, j + 3u) = Unsafe.Add(ref pBase, p3); - } - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14)); } -internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 +/// +/// Reorders padded XYZW components to WZYX. +/// +internal readonly struct WZYXPad3Shuffle4 : IPad3Shuffle4 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); + public static uint Invoke(uint source) => BinaryPrimitives.ReverseEndianness(source); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); +} + +/// +/// Reorders padded XYZW components to ZYXW. +/// +internal readonly struct ZYXWPad3Shuffle4 : IPad3Shuffle4 +{ + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + public static uint Invoke(uint source) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - ref byte sEnd = ref Unsafe.Add(ref sBase, (uint)source.Length); - ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - // The fast scalar path reads one extra byte past the source triplet. - // Keep that widened read in a local before writing the expanded pixel - // so overlapping destinations cannot change what was read. - uint packed = Unsafe.As(ref sBase) | 0xFF000000; - - Unsafe.As(ref dBase) = packed; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - // The final triplet cannot use the widened read above, so assemble - // the same padded uint byte-by-byte before the overlapping store. - uint packed = - Unsafe.Add(ref sBase, 0u) | - ((uint)Unsafe.Add(ref sBase, 1u) << 8) | - ((uint)Unsafe.Add(ref sBase, 2u) << 16) | - 0xFF000000; - - Unsafe.As(ref dBase) = packed; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } + // Preserve opaque W and Y while exchanging X and Z. + uint wy = source & 0xFF00FF00; + uint xz = source & 0x00FF00FF; + return wy | BitOperations.RotateLeft(xz, 16); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs index 3907df58c..eab5a41da 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs @@ -1,51 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; -/// +/// +/// Identifies a stateless three-component shuffle operator. +/// internal interface IShuffle3 : IComponentShuffle { } -internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShuffle3 +/// +/// Reorders XYZ components to ZYX. +/// +internal readonly struct ZYXShuffle3 : IShuffle3 { - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle3Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + public static uint Invoke(uint source) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0; i < (uint)source.Length; i += 3) - { - // The scalar remainder can run in-place after the vector body. Load - // the full 3-byte pixel into a register-sized value before stores so - // channel swaps cannot corrupt later reads from the same pixel. - uint packed = - Unsafe.Add(ref sBase, i + 0u) | - ((uint)Unsafe.Add(ref sBase, i + 1u) << 8) | - ((uint)Unsafe.Add(ref sBase, i + 2u) << 16); - - ref byte pBase = ref Unsafe.As(ref packed); - - Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); - Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); - Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); - } + // Y is already centered; shift X and Z directly into each other's byte positions. + uint y = source & 0x0000FF00; + uint x = (source & 0x000000FF) << 16; + uint z = (source & 0x00FF0000) >> 16; + return x | y | z; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs index 68f34efd7..56ea59df5 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs @@ -2,183 +2,320 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; -using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; -/// +/// +/// Defines a stateless operation over one packed four-component pixel. +/// internal interface IShuffle4 : IComponentShuffle { + /// + /// Reorders the packed pixels in a 256-bit vector. + /// + /// The source pixels. + /// The reordered pixels. + static abstract Vector256 Invoke(Vector256 source); + + /// + /// Reorders the packed pixels in a 512-bit vector. + /// + /// The source pixels. + /// The reordered pixels. + static abstract Vector512 Invoke(Vector512 source); } -internal readonly struct DefaultShuffle4([ConstantExpected] byte control) : IShuffle4 +/// +/// Reorders XYZW components to WXYZ. +/// +internal readonly struct WXYZShuffle4 : IShuffle4 { - public byte Control { get; } = control; - + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter + public static uint Invoke(uint source) + { + // source = [W Z Y X] + // ROTL(8, source) = [Z Y X W] + return BitOperations.RotateLeft(source, 8); + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, CreateMask()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 source) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); - - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); - - for (nuint i = 0; i < (uint)source.Length; i += 4) - { - // The generic path may be used with source and destination pointing - // at the same pixel. Load all channels first so subsequent stores - // index only staged bytes, matching the specialized uint shuffles. - uint packed = Unsafe.As(ref Unsafe.Add(ref sBase, i)); - ref byte pBase = ref Unsafe.As(ref packed); - - Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); - Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); - Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); - Unsafe.Add(ref dBase, i + 3u) = Unsafe.Add(ref pBase, p3); - } + // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. + Vector128 mask = CreateMask(); + return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } -} -internal readonly struct WXYZShuffle4 : IShuffle4 -{ - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle2103); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 source) + => Vector512_.ShuffleNative(source, CreateMask512()); - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + /// Creates the indices that rotate each XYZW pixel to WXYZ within one 128-bit lane. + /// + /// The pixel-local byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 CreateMask() + => Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14); + + /// + /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. + /// + /// The absolute byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateMask512() { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); - } + // Native vpshufb ignores the lane offsets, while Vector512.Shuffle treats the indices as + // absolute. Encoding both meanings keeps the AVX-512 and managed fallback results identical. + return Vector512.Create( + 0x0605040702010003UL, + 0x0E0D0C0F0A09080BUL, + 0x1615141712111013UL, + 0x1E1D1C1F1A19181BUL, + 0x2625242722212023UL, + 0x2E2D2C2F2A29282BUL, + 0x3635343732313033UL, + 0x3E3D3C3F3A39383BUL).AsByte(); } } +/// +/// Reorders XYZW components to WZYX. +/// internal readonly struct WZYXShuffle4 : IShuffle4 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0123); + public static uint Invoke(uint source) + { + // Reversing the integer's endianness also reverses the four byte components. + return BinaryPrimitives.ReverseEndianness(source); + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, CreateMask()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 source) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); - } + Vector128 mask = CreateMask(); + return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 source) + => Vector512_.ShuffleNative(source, CreateMask512()); + + /// + /// Creates the indices that reverse each XYZW pixel to WZYX within one 128-bit lane. + /// + /// The pixel-local byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 CreateMask() + => Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12); + + /// + /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. + /// + /// The absolute byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateMask512() + => Vector512.Create( + 0x0405060700010203UL, + 0x0C0D0E0F08090A0BUL, + 0x1415161710111213UL, + 0x1C1D1E1F18191A1BUL, + 0x2425262720212223UL, + 0x2C2D2E2F28292A2BUL, + 0x3435363730313233UL, + 0x3C3D3E3F38393A3BUL).AsByte(); } +/// +/// Reorders XYZW components to YZWX. +/// internal readonly struct YZWXShuffle4 : IShuffle4 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0321); + public static uint Invoke(uint source) + { + // source = [W Z Y X] + // ROTR(8, source) = [X W Z Y] + return BitOperations.RotateRight(source, 8); + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, CreateMask()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 source) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8); - } + Vector128 mask = CreateMask(); + return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 source) + => Vector512_.ShuffleNative(source, CreateMask512()); + + /// + /// Creates the indices that rotate each XYZW pixel to YZWX within one 128-bit lane. + /// + /// The pixel-local byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 CreateMask() + => Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12); + + /// + /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. + /// + /// The absolute byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateMask512() + => Vector512.Create( + 0x0407060500030201UL, + 0x0C0F0E0D080B0A09UL, + 0x1417161510131211UL, + 0x1C1F1E1D181B1A19UL, + 0x2427262520232221UL, + 0x2C2F2E2D282B2A29UL, + 0x3437363530333231UL, + 0x3C3F3E3D383B3A39UL).AsByte(); } +/// +/// Reorders XYZW components to ZYXW. +/// internal readonly struct ZYXWShuffle4 : IShuffle4 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3012); + public static uint Invoke(uint source) + { + // Preserve W and Y while rotating the masked X/Z bytes into each other's positions. + uint wy = source & 0xFF00FF00; + uint xz = source & 0x00FF00FF; + return wy | BitOperations.RotateLeft(xz, 16); + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, CreateMask()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 source) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [W 0 Y 0] - // tmp2 = [0 Z 0 X] - // tmp3=ROTL(16, tmp2) = [0 X 0 Z] - // tmp1 + tmp3 = [W X Y Z] - uint tmp1 = packed & 0xFF00FF00; - uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } + Vector128 mask = CreateMask(); + return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 source) + => Vector512_.ShuffleNative(source, CreateMask512()); + + /// + /// Creates the indices that exchange X and Z in each XYZW pixel within one 128-bit lane. + /// + /// The pixel-local byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 CreateMask() + => Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); + + /// + /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. + /// + /// The absolute byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateMask512() + => Vector512.Create( + 0x0704050603000102UL, + 0x0F0C0D0E0B08090AUL, + 0x1714151613101112UL, + 0x1F1C1D1E1B18191AUL, + 0x2724252623202122UL, + 0x2F2C2D2E2B28292AUL, + 0x3734353633303132UL, + 0x3F3C3D3E3B38393AUL).AsByte(); } +/// +/// Reorders XYZW components to XWZY. +/// internal readonly struct XWZYShuffle4 : IShuffle4 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle1230); + public static uint Invoke(uint source) + { + // Preserve X and Z while rotating the masked Y/W bytes into each other's positions. + uint xz = source & 0x00FF00FF; + uint yw = source & 0xFF00FF00; + return xz | BitOperations.RotateLeft(yw, 16); + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, CreateMask()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 source) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - uint n = (uint)source.Length / 4; - - for (nuint i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [0 Z 0 X] - // tmp2 = [W 0 Y 0] - // tmp3=ROTL(16, tmp2) = [Y 0 W 0] - // tmp1 + tmp3 = [Y Z W X] - uint tmp1 = packed & 0x00FF00FF; - uint tmp2 = packed & 0xFF00FF00; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } + Vector128 mask = CreateMask(); + return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 source) + => Vector512_.ShuffleNative(source, CreateMask512()); + + /// + /// Creates the indices that exchange Y and W in each XYZW pixel within one 128-bit lane. + /// + /// The pixel-local byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 CreateMask() + => Vector128.Create((byte)0, 3, 2, 1, 4, 7, 6, 5, 8, 11, 10, 9, 12, 15, 14, 13); + + /// + /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. + /// + /// The absolute byte shuffle indices. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateMask512() + => Vector512.Create( + 0x0506070401020300UL, + 0x0D0E0F0C090A0B08UL, + 0x1516171411121310UL, + 0x1D1E1F1C191A1B18UL, + 0x2526272421222320UL, + 0x2D2E2F2C292A2B28UL, + 0x3536373431323330UL, + 0x3D3E3F3C393A3B38UL).AsByte(); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index 613406167..17ec503e5 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -1,101 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; +using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static SixLabors.ImageSharp.SimdUtils; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; -/// +/// +/// Defines a stateless operation that reorders four packed components before retaining three. +/// internal interface IShuffle4Slice3 : IComponentShuffle { } -internal readonly struct DefaultShuffle4Slice3([ConstantExpected] byte control) : IShuffle4Slice3 +/// +/// Preserves XYZ order and discards W. +/// +internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 { - public byte Control { get; } = control; - - [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) -#pragma warning disable CA1857 // A constant is expected for the parameter - => HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, this.Control); -#pragma warning restore CA1857 // A constant is expected for the parameter - + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(destination); + public static uint Invoke(uint source) => source; - SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) => source; +} - for (nuint i = 0, j = 0; i < (uint)destination.Length; i += 3, j += 4) - { - // Shrinking 4-byte pixels to 3 bytes can still be called in-place by - // tail code. Read the complete source pixel first, then write only - // the requested channels into the destination triplet. - uint packed = Unsafe.As(ref Unsafe.Add(ref sBase, j)); - ref byte pBase = ref Unsafe.As(ref packed); +/// +/// Reorders XYZW components to YZW before discarding X. +/// +internal readonly struct YZWXShuffle4Slice3 : IShuffle4Slice3 +{ + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Invoke(uint source) => BitOperations.RotateRight(source, 8); - Unsafe.Add(ref dBase, i + 0u) = Unsafe.Add(ref pBase, p0); - Unsafe.Add(ref dBase, i + 1u) = Unsafe.Add(ref pBase, p1); - Unsafe.Add(ref dBase, i + 2u) = Unsafe.Add(ref pBase, p2); - } - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12)); } -internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 +/// +/// Reorders XYZW components to WZY before discarding X. +/// +internal readonly struct WZYXShuffle4Slice3 : IShuffle4Slice3 { + /// [MethodImpl(InliningOptions.ShortMethod)] - public void ShuffleReduce(ref ReadOnlySpan source, ref Span destination) - => HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); + public static uint Invoke(uint source) => BinaryPrimitives.ReverseEndianness(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); +} +/// +/// Reorders XYZW components to ZYX before discarding W. +/// +internal readonly struct ZYXWShuffle4Slice3 : IShuffle4Slice3 +{ + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Shuffle(ReadOnlySpan source, Span destination) + public static uint Invoke(uint source) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Byte3 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - - nint n = (nint)(uint)source.Length / 4; - nint m = Numerics.Modulo4(n); - nint u = n - m; - - ref uint sLoopEnd = ref Unsafe.Add(ref sBase, u); - ref uint sEnd = ref Unsafe.Add(ref sBase, n); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - // Stage the four source pixels before the 3-byte stores. Even - // though this path preserves XYZ order, the packed loads must happen - // before destination writes when the spans overlap. - uint packed0 = Unsafe.Add(ref sBase, 0u); - uint packed1 = Unsafe.Add(ref sBase, 1u); - uint packed2 = Unsafe.Add(ref sBase, 2u); - uint packed3 = Unsafe.Add(ref sBase, 3u); - - Unsafe.Add(ref dBase, 0u) = Unsafe.As(ref packed0); - Unsafe.Add(ref dBase, 1u) = Unsafe.As(ref packed1); - Unsafe.Add(ref dBase, 2u) = Unsafe.As(ref packed2); - Unsafe.Add(ref dBase, 3u) = Unsafe.As(ref packed3); - - sBase = ref Unsafe.Add(ref sBase, 4); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - // Same overlap rule as the unrolled loop: take the 4-byte source - // pixel before storing the 3-byte destination value. - uint packed = Unsafe.Add(ref sBase, 0u); - - Unsafe.Add(ref dBase, 0u) = Unsafe.As(ref packed); - - sBase = ref Unsafe.Add(ref sBase, 1); - dBase = ref Unsafe.Add(ref dBase, 1); - } + // Preserve W and Y while exchanging X and Z; W is subsequently discarded. + uint wy = source & 0xFF00FF00; + uint xz = source & 0x00FF00FF; + return wy | BitOperations.RotateLeft(xz, 16); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 source) + => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } [StructLayout(LayoutKind.Explicit, Size = 3)] diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index 8b2baec21..e709cc8d4 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -6,6 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp; @@ -42,22 +44,104 @@ internal static partial class SimdUtils /// The type of shuffle struct. /// The source span of bytes. /// The destination span of bytes. - /// The type of shuffle to perform. [MethodImpl(InliningOptions.ShortMethod)] public static void Shuffle4( ReadOnlySpan source, - Span destination, - TShuffle shuffle) + Span destination) where TShuffle : struct, IShuffle4 { VerifyShuffle4SpanInput(source, destination); - shuffle.ShuffleReduce(ref source, ref destination); + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref byte destinationBase = ref MemoryMarshal.GetReference(destination); + int length = source.Length; + int i = 0; - // Deal with the remainder: - if (source.Length > 0) + // The same offset flows through descending widths. This keeps a single traversal while + // allowing a row that is not a multiple of the widest register to retain a vectorized tail. + if (Vector512.IsHardwareAccelerated) + { + int fourVectorsFromEnd = length - (Vector512.Count * 4); + + for (; i <= fourVectorsFromEnd; i += Vector512.Count * 4) + { + // Four independent vectors amortize loop control and expose enough work for the CPU + // to overlap loads, byte shuffles, and stores without changing pixel ordering. + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))) + .StoreUnsafe(ref destinationBase, (nuint)(i + Vector512.Count)); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 2)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 2))); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 3)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 3))); + } + + int oneVectorFromEnd = length - Vector512.Count; + + for (; i <= oneVectorFromEnd; i += Vector512.Count) + { + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int fourVectorsFromEnd = length - (Vector256.Count * 4); + + for (; i <= fourVectorsFromEnd; i += Vector256.Count * 4) + { + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))) + .StoreUnsafe(ref destinationBase, (nuint)(i + Vector256.Count)); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 2)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 2))); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 3)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 3))); + } + + int oneVectorFromEnd = length - Vector256.Count; + + for (; i <= oneVectorFromEnd; i += Vector256.Count) + { + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) { - shuffle.Shuffle(source, destination); + int fourVectorsFromEnd = length - (Vector128.Count * 4); + + for (; i <= fourVectorsFromEnd; i += Vector128.Count * 4) + { + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))) + .StoreUnsafe(ref destinationBase, (nuint)(i + Vector128.Count)); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 2)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 2))); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 3)))) + .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 3))); + } + + int oneVectorFromEnd = length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)) + .StoreUnsafe(ref destinationBase, (nuint)i); + } + } + + // The vector cascade leaves fewer than four pixels. A full uint load keeps each pixel + // in a register while the closed operator resolves to its rotate, reverse, or mask sequence. + for (; i < length; i += 4) + { + uint packed = Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)i)); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)i)) = TShuffle.Invoke(packed); } } @@ -68,23 +152,112 @@ internal static partial class SimdUtils /// The type of shuffle struct. /// The source span of bytes. /// The destination span of bytes. - /// The type of shuffle to perform. [MethodImpl(InliningOptions.ShortMethod)] public static void Shuffle3( ReadOnlySpan source, - Span destination, - TShuffle shuffle) + Span destination) where TShuffle : struct, IShuffle3 { - // Source length should be smaller than destination length, and divisible by 3. VerifyShuffle3SpanInput(source, destination); - shuffle.ShuffleReduce(ref source, ref destination); + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref byte destinationBase = ref MemoryMarshal.GetReference(destination); + int length = source.Length; + int i = 0; - // Deal with the remainder: - if (source.Length > 0) + if (Vector128.IsHardwareAccelerated) + { + // Each group contains sixteen XYZ pixels in three registers. The pad mask expands + // four triplets per register to XYZW, with 0x80 selecting zero for the temporary W lane. + Vector128 padMask = Vector128.Create( + (byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); + + // After the operator has reordered padded pixels, these masks remove every temporary + // W lane and repack the four registers into three contiguous XYZ destination registers. + Vector128 sliceMask = Vector128.Create( + (byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); + Vector128 sliceEndMask = Vector128_.AlignRight(sliceMask, sliceMask, 12); + ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); + ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); + nuint sourceVectorCount = (uint)length / (uint)Vector128.Count; + nuint vectorIndex = 0; + + for (; vectorIndex + 2 < sourceVectorCount; vectorIndex += 3) + { + // Realign the three source registers into four registers holding four complete + // triplets apiece. All source registers are captured before any destination store. + ref Vector128 source0 = ref Unsafe.Add(ref sourceVectors, vectorIndex); + Vector128 v0 = source0; + Vector128 v1 = Unsafe.Add(ref source0, 1); + Vector128 v2 = Unsafe.Add(ref source0, 2); + Vector128 v3 = Vector128_.ShiftRightBytesInVector(v2, 4); + + v2 = Vector128_.AlignRight(v2, v1, 8); + v1 = Vector128_.AlignRight(v1, v0, 12); + + v0 = TShuffle.Invoke(Vector128_.ShuffleNative(v0, padMask)); + v1 = TShuffle.Invoke(Vector128_.ShuffleNative(v1, padMask)); + v2 = TShuffle.Invoke(Vector128_.ShuffleNative(v2, padMask)); + v3 = TShuffle.Invoke(Vector128_.ShuffleNative(v3, padMask)); + + v0 = Vector128_.ShuffleNative(v0, sliceEndMask); + v1 = Vector128_.ShuffleNative(v1, sliceMask); + v2 = Vector128_.ShuffleNative(v2, sliceEndMask); + v3 = Vector128_.ShuffleNative(v3, sliceMask); + + Vector128 destination0 = Vector128_.AlignRight(v1, v0, 4); + Vector128 destination2 = Vector128_.AlignRight(v3, v2, 12); + v1 = Vector128_.ShiftLeftBytesInVector(v1, 4); + v2 = Vector128_.ShiftRightBytesInVector(v2, 4); + Vector128 destination1 = Vector128_.AlignRight(v2, v1, 8); + + ref Vector128 destination0Ref = ref Unsafe.Add(ref destinationVectors, vectorIndex); + destination0Ref = destination0; + Unsafe.Add(ref destination0Ref, 1) = destination1; + Unsafe.Add(ref destination0Ref, 2) = destination2; + } + + i = (int)(vectorIndex * (uint)Vector128.Count); + int oneTailVectorFromEnd = length - Vector128.Count; + + for (; i <= oneTailVectorFromEnd; i += 12) + { + // A single readable register contains four complete triplets plus four bytes from + // the following pixels. The pad mask ignores those extra bytes before the operator + // runs, and the slice mask packs the four results into the low twelve bytes. + Vector128 result = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + result = Vector128_.ShuffleNative(result, padMask); + result = TShuffle.Invoke(result); + result = Vector128_.ShuffleNative(result, sliceMask); + + // Store exactly twelve bytes so an in-place shuffle does not overwrite the next + // source triplet captured by the following iteration. + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (nuint)i)) = result.GetLower(); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)(i + 8))) = result.AsUInt32().GetElement(2); + } + } + + int widenedReadEnd = length - 3; + + for (; i < widenedReadEnd; i += 3) { - shuffle.Shuffle(source, destination); + // The fourth byte belongs to the following pixel, but the operator only contributes the + // low three result bytes. This unaligned read replaces three dependent byte loads safely. + uint packed = Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)i)); + uint shuffled = TShuffle.Invoke(packed); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)i)) = Unsafe.As(ref shuffled); + } + + if (i < length) + { + // The final triplet has no fourth readable byte, so construct only this terminal pixel. + uint packed = + Unsafe.Add(ref sourceBase, (nuint)i) | + ((uint)Unsafe.Add(ref sourceBase, (nuint)(i + 1)) << 8) | + ((uint)Unsafe.Add(ref sourceBase, (nuint)(i + 2)) << 16); + + uint shuffled = TShuffle.Invoke(packed); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)i)) = Unsafe.As(ref shuffled); } } @@ -95,22 +268,78 @@ internal static partial class SimdUtils /// The type of shuffle struct. /// The source span of bytes. /// The destination span of bytes. - /// The type of shuffle to perform. [MethodImpl(InliningOptions.ShortMethod)] public static void Pad3Shuffle4( ReadOnlySpan source, - Span destination, - TShuffle shuffle) + Span destination) where TShuffle : struct, IPad3Shuffle4 { VerifyPad3Shuffle4SpanInput(source, destination); - shuffle.ShuffleReduce(ref source, ref destination); + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref byte destinationBase = ref MemoryMarshal.GetReference(destination); + int sourceLength = source.Length; + int sourceOffset = 0; + int destinationOffset = 0; - // Deal with the remainder: - if (source.Length > 0) + if (Vector128.IsHardwareAccelerated) + { + // The fixed mask expands four XYZ triplets to four XYZW pixels. The zeroed W bytes + // are then filled with opaque alpha before the selected operator reorders each pixel. + Vector128 padMask = Vector128.Create( + (byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); + Vector128 opaqueAlpha = Vector128.Create(0xFF000000FF000000UL).AsByte(); + ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); + ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); + nuint sourceVectorCount = (uint)sourceLength / (uint)Vector128.Count; + nuint sourceVectorIndex = 0; + nuint destinationVectorIndex = 0; + + for (; sourceVectorIndex + 2 < sourceVectorCount; + sourceVectorIndex += 3, destinationVectorIndex += 4) + { + // Three source registers contain sixteen packed triplets. Aligning at 12, 8, and + // 4-byte boundaries produces four registers whose low twelve bytes each hold four pixels. + ref Vector128 source0 = ref Unsafe.Add(ref sourceVectors, sourceVectorIndex); + Vector128 v0 = source0; + Vector128 v1 = Unsafe.Add(ref source0, 1); + Vector128 v2 = Unsafe.Add(ref source0, 2); + Vector128 v3 = Vector128_.ShiftRightBytesInVector(v2, 4); + + v2 = Vector128_.AlignRight(v2, v1, 8); + v1 = Vector128_.AlignRight(v1, v0, 12); + + ref Vector128 destination0 = ref Unsafe.Add(ref destinationVectors, destinationVectorIndex); + destination0 = TShuffle.Invoke(Vector128_.ShuffleNative(v0, padMask) | opaqueAlpha); + Unsafe.Add(ref destination0, 1) = TShuffle.Invoke(Vector128_.ShuffleNative(v1, padMask) | opaqueAlpha); + Unsafe.Add(ref destination0, 2) = TShuffle.Invoke(Vector128_.ShuffleNative(v2, padMask) | opaqueAlpha); + Unsafe.Add(ref destination0, 3) = TShuffle.Invoke(Vector128_.ShuffleNative(v3, padMask) | opaqueAlpha); + } + + sourceOffset = (int)(sourceVectorIndex * (uint)Vector128.Count); + destinationOffset = (int)(destinationVectorIndex * (uint)Vector128.Count); + } + + int widenedReadEnd = sourceLength - 3; + + for (; sourceOffset < widenedReadEnd; sourceOffset += 3, destinationOffset += 4) + { + // The widened load intentionally includes the next pixel's first byte. Replacing that + // high byte with opaque alpha yields the complete XYZW value with one unaligned read. + uint packed = Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)sourceOffset)) | 0xFF000000; + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = TShuffle.Invoke(packed); + } + + if (sourceOffset < sourceLength) { - shuffle.Shuffle(source, destination); + // The final triplet cannot use the widened load because no following byte is in range. + uint packed = + Unsafe.Add(ref sourceBase, (nuint)sourceOffset) | + ((uint)Unsafe.Add(ref sourceBase, (nuint)(sourceOffset + 1)) << 8) | + ((uint)Unsafe.Add(ref sourceBase, (nuint)(sourceOffset + 2)) << 16) | + 0xFF000000; + + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = TShuffle.Invoke(packed); } } @@ -121,22 +350,101 @@ internal static partial class SimdUtils /// The type of shuffle struct. /// The source span of bytes. /// The destination span of bytes. - /// The type of shuffle to perform. [MethodImpl(InliningOptions.ShortMethod)] public static void Shuffle4Slice3( ReadOnlySpan source, - Span destination, - TShuffle shuffle) + Span destination) where TShuffle : struct, IShuffle4Slice3 { VerifyShuffle4Slice3SpanInput(source, destination); - shuffle.ShuffleReduce(ref source, ref destination); + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref byte destinationBase = ref MemoryMarshal.GetReference(destination); + int sourceLength = source.Length; + int sourceOffset = 0; + int destinationOffset = 0; - // Deal with the remainder: - if (source.Length > 0) + if (Vector128.IsHardwareAccelerated) + { + // Each operator first places the three retained components in the low bytes of every + // four-byte pixel. These masks then delete the fourth byte and compact sixteen pixels. + Vector128 sliceMask = Vector128.Create( + (byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); + Vector128 sliceEndMask = Vector128_.AlignRight(sliceMask, sliceMask, 12); + ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); + ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); + nuint sourceVectorCount = (uint)sourceLength / (uint)Vector128.Count; + nuint sourceVectorIndex = 0; + nuint destinationVectorIndex = 0; + + for (; sourceVectorIndex + 3 < sourceVectorCount; + sourceVectorIndex += 4, destinationVectorIndex += 3) + { + // Load and transform all sixteen source pixels before writing the shorter output group. + // This preserves forward progress when source and destination begin at the same address. + ref Vector128 source0 = ref Unsafe.Add(ref sourceVectors, sourceVectorIndex); + Vector128 v0 = TShuffle.Invoke(source0); + Vector128 v1 = TShuffle.Invoke(Unsafe.Add(ref source0, 1)); + Vector128 v2 = TShuffle.Invoke(Unsafe.Add(ref source0, 2)); + Vector128 v3 = TShuffle.Invoke(Unsafe.Add(ref source0, 3)); + + v0 = Vector128_.ShuffleNative(v0, sliceEndMask); + v1 = Vector128_.ShuffleNative(v1, sliceMask); + v2 = Vector128_.ShuffleNative(v2, sliceEndMask); + v3 = Vector128_.ShuffleNative(v3, sliceMask); + + Vector128 destination0 = Vector128_.AlignRight(v1, v0, 4); + Vector128 destination2 = Vector128_.AlignRight(v3, v2, 12); + v1 = Vector128_.ShiftLeftBytesInVector(v1, 4); + v2 = Vector128_.ShiftRightBytesInVector(v2, 4); + Vector128 destination1 = Vector128_.AlignRight(v2, v1, 8); + + ref Vector128 destination0Ref = ref Unsafe.Add(ref destinationVectors, destinationVectorIndex); + destination0Ref = destination0; + Unsafe.Add(ref destination0Ref, 1) = destination1; + Unsafe.Add(ref destination0Ref, 2) = destination2; + } + + sourceOffset = (int)(sourceVectorIndex * (uint)Vector128.Count); + destinationOffset = (int)(destinationVectorIndex * (uint)Vector128.Count); + int oneTailVectorFromEnd = sourceLength - Vector128.Count; + + for (; sourceOffset <= oneTailVectorFromEnd; sourceOffset += 16, destinationOffset += 12) + { + // The operator arranges the three retained components at the front of each pixel. + // One fixed shuffle then compacts four pixels into the low twelve vector bytes. + Vector128 result = TShuffle.Invoke( + Vector128.LoadUnsafe(ref sourceBase, (nuint)sourceOffset)); + + result = Vector128_.ShuffleNative(result, sliceMask); + + // The split store writes the exact 12-byte result and remains safe for in-place shrinking. + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = result.GetLower(); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)(destinationOffset + 8))) = result.AsUInt32().GetElement(2); + } + } + + int fourPixelsFromEnd = sourceLength - 16; + + for (; sourceOffset <= fourPixelsFromEnd; sourceOffset += 16, destinationOffset += 12) + { + // Transform four complete pixels before the first three-byte store. Keeping the source + // values in registers avoids reloads after an in-place shrinking destination advances. + uint packed0 = TShuffle.Invoke(Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)sourceOffset))); + uint packed1 = TShuffle.Invoke(Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)(sourceOffset + 4)))); + uint packed2 = TShuffle.Invoke(Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)(sourceOffset + 8)))); + uint packed3 = TShuffle.Invoke(Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)(sourceOffset + 12)))); + + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = Unsafe.As(ref packed0); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)(destinationOffset + 3))) = Unsafe.As(ref packed1); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)(destinationOffset + 6))) = Unsafe.As(ref packed2); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)(destinationOffset + 9))) = Unsafe.As(ref packed3); + } + + for (; sourceOffset < sourceLength; sourceOffset += 4, destinationOffset += 3) { - shuffle.Shuffle(source, destination); + uint packed = TShuffle.Invoke(Unsafe.As(ref Unsafe.Add(ref sourceBase, (nuint)sourceOffset))); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = Unsafe.As(ref packed); } } diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 50a68906e..c25cc301c 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -33,7 +33,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -44,7 +44,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -55,7 +55,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -66,7 +66,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -77,7 +77,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -96,7 +96,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -107,7 +107,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -118,7 +118,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -129,7 +129,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -140,7 +140,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -159,7 +159,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -170,7 +170,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -181,7 +181,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -192,7 +192,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -203,7 +203,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -222,7 +222,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -233,7 +233,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -244,7 +244,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -255,7 +255,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -266,7 +266,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -285,7 +285,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -296,7 +296,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -307,7 +307,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -318,7 +318,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -329,7 +329,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle3(source, dest); } /// @@ -348,7 +348,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -359,7 +359,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -370,7 +370,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -381,7 +381,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -392,6 +392,6 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle3(source, dest); } } diff --git a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs index 8728dd671..16529306a 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Pad3Shuffle4Channel { - private static readonly DefaultPad3Shuffle4 Control = new(SimdUtils.Shuffle.MMShuffle1032); private byte[] source; private byte[] destination; @@ -25,11 +24,11 @@ public class Pad3Shuffle4Channel [Benchmark] public void Pad3Shuffle4() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, Control); + => SimdUtils.Pad3Shuffle4(this.source, this.destination); [Benchmark] public void Pad3Shuffle4FastFallback() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, default(XYZWPad3Shuffle4)); + => SimdUtils.Pad3Shuffle4(this.source, this.destination); } // 2020-10-30 diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs index e4c12900f..e09c4ce6c 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Shuffle3Channel { - private static readonly DefaultShuffle3 Control = new(SimdUtils.Shuffle.MMShuffle3102); private byte[] source; private byte[] destination; @@ -25,7 +24,7 @@ public class Shuffle3Channel [Benchmark] public void Shuffle3() - => SimdUtils.Shuffle3(this.source, this.destination, Control); + => SimdUtils.Shuffle3(this.source, this.destination); } // 2020-11-02 diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs index 579e2c54d..4b694768d 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Shuffle4Slice3Channel { - private static readonly DefaultShuffle4Slice3 Control = new(SimdUtils.Shuffle.MMShuffle1032); private byte[] source; private byte[] destination; @@ -25,11 +24,11 @@ public class Shuffle4Slice3Channel [Benchmark] public void Shuffle4Slice3() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, Control); + => SimdUtils.Shuffle4Slice3(this.source, this.destination); [Benchmark] public void Shuffle4Slice3FastFallback() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, default(XYZWShuffle4Slice3)); + => SimdUtils.Shuffle4Slice3(this.source, this.destination); } // 2020-10-29 diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs index 6a16bb571..f3ad974c4 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs @@ -24,7 +24,7 @@ public class ShuffleByte4Channel [Benchmark] public void Shuffle4Channel() - => SimdUtils.Shuffle4(this.source, this.destination, default); + => SimdUtils.Shuffle4(this.source, this.destination); } // 2020-10-29 diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs new file mode 100644 index 000000000..a7dcb9021 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs @@ -0,0 +1,222 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Measures every optimized conversion between the six byte-packed RGB and RGBA pixel layouts. +/// +[Config(typeof(Config.Short))] +public class PackedPixelConversion +{ + private byte[] source3; + private byte[] source4; + private byte[] destination3; + private byte[] destination4; + + /// + /// Gets or sets the number of pixels converted by each invocation. + /// + [Params(7, 256, 4096)] + public int Count { get; set; } + + /// + /// Creates deterministic source buffers and correctly sized destination buffers. + /// + [GlobalSetup] + public void Setup() + { + this.source3 = new byte[this.Count * 3]; + this.source4 = new byte[this.Count * 4]; + this.destination3 = new byte[this.Count * 3]; + this.destination4 = new byte[this.Count * 4]; + + // Non-repeating channel values prevent the JIT or hardware from benefiting from + // zero-filled inputs while keeping both benchmark revisions byte-for-byte identical. + new Random(42).NextBytes(this.source3); + new Random(42).NextBytes(this.source4); + } + + /// + /// Converts RGBA pixels to ARGB pixels. + /// + [Benchmark] + public void Rgba32ToArgb32() => PixelConverter.FromRgba32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to ABGR pixels. + /// + [Benchmark] + public void Rgba32ToAbgr32() => PixelConverter.FromRgba32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to BGRA pixels. + /// + [Benchmark] + public void Rgba32ToBgra32() => PixelConverter.FromRgba32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to RGB pixels. + /// + [Benchmark] + public void Rgba32ToRgb24() => PixelConverter.FromRgba32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts RGBA pixels to BGR pixels. + /// + [Benchmark] + public void Rgba32ToBgr24() => PixelConverter.FromRgba32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts ARGB pixels to RGBA pixels. + /// + [Benchmark] + public void Argb32ToRgba32() => PixelConverter.FromArgb32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to ABGR pixels. + /// + [Benchmark] + public void Argb32ToAbgr32() => PixelConverter.FromArgb32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to BGRA pixels. + /// + [Benchmark] + public void Argb32ToBgra32() => PixelConverter.FromArgb32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to RGB pixels. + /// + [Benchmark] + public void Argb32ToRgb24() => PixelConverter.FromArgb32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts ARGB pixels to BGR pixels. + /// + [Benchmark] + public void Argb32ToBgr24() => PixelConverter.FromArgb32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts ABGR pixels to RGBA pixels. + /// + [Benchmark] + public void Abgr32ToRgba32() => PixelConverter.FromAbgr32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to ARGB pixels. + /// + [Benchmark] + public void Abgr32ToArgb32() => PixelConverter.FromAbgr32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to BGRA pixels. + /// + [Benchmark] + public void Abgr32ToBgra32() => PixelConverter.FromAbgr32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to RGB pixels. + /// + [Benchmark] + public void Abgr32ToRgb24() => PixelConverter.FromAbgr32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts ABGR pixels to BGR pixels. + /// + [Benchmark] + public void Abgr32ToBgr24() => PixelConverter.FromAbgr32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts BGRA pixels to RGBA pixels. + /// + [Benchmark] + public void Bgra32ToRgba32() => PixelConverter.FromBgra32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to ARGB pixels. + /// + [Benchmark] + public void Bgra32ToArgb32() => PixelConverter.FromBgra32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to ABGR pixels. + /// + [Benchmark] + public void Bgra32ToAbgr32() => PixelConverter.FromBgra32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to RGB pixels. + /// + [Benchmark] + public void Bgra32ToRgb24() => PixelConverter.FromBgra32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts BGRA pixels to BGR pixels. + /// + [Benchmark] + public void Bgra32ToBgr24() => PixelConverter.FromBgra32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts RGB pixels to RGBA pixels. + /// + [Benchmark] + public void Rgb24ToRgba32() => PixelConverter.FromRgb24.ToRgba32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to ARGB pixels. + /// + [Benchmark] + public void Rgb24ToArgb32() => PixelConverter.FromRgb24.ToArgb32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to ABGR pixels. + /// + [Benchmark] + public void Rgb24ToAbgr32() => PixelConverter.FromRgb24.ToAbgr32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to BGRA pixels. + /// + [Benchmark] + public void Rgb24ToBgra32() => PixelConverter.FromRgb24.ToBgra32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to BGR pixels. + /// + [Benchmark] + public void Rgb24ToBgr24() => PixelConverter.FromRgb24.ToBgr24(this.source3, this.destination3); + + /// + /// Converts BGR pixels to RGBA pixels. + /// + [Benchmark] + public void Bgr24ToRgba32() => PixelConverter.FromBgr24.ToRgba32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to ARGB pixels. + /// + [Benchmark] + public void Bgr24ToArgb32() => PixelConverter.FromBgr24.ToArgb32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to ABGR pixels. + /// + [Benchmark] + public void Bgr24ToAbgr32() => PixelConverter.FromBgr24.ToAbgr32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to BGRA pixels. + /// + [Benchmark] + public void Bgr24ToBgra32() => PixelConverter.FromBgr24.ToBgra32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to RGB pixels. + /// + [Benchmark] + public void Bgr24ToRgb24() => PixelConverter.FromBgr24.ToRgb24(this.source3, this.destination3); +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs new file mode 100644 index 000000000..b93e40e19 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Exposes every stateless packed-pixel shuffle operator for assembly inspection. +/// +/// +/// Seven pixels expose the short-input and vector-tail paths. Seventeen pixels execute one +/// full intrinsic group and leave one complete pixel for the scalar remainder, making every +/// part of each generated traversal observable. +/// +[Config(typeof(Config.Analysis))] +public class PackedPixelConversionAssembly +{ + private byte[] source3; + private byte[] source4; + private byte[] destination3; + private byte[] destination4; + + /// + /// Gets or sets the number of pixels converted by each invocation. + /// + [Params(7, 17)] + public int Count { get; set; } + + /// + /// Populates the source buffers with deterministic non-uniform channel values. + /// + [GlobalSetup] + public void Setup() + { + this.source3 = new byte[this.Count * 3]; + this.source4 = new byte[this.Count * 4]; + this.destination3 = new byte[this.Count * 3]; + this.destination4 = new byte[this.Count * 4]; + + new Random(42).NextBytes(this.source3); + new Random(42).NextBytes(this.source4); + } + + /// + /// Executes the WXYZ four-to-four operator. + /// + [Benchmark] + public void Shuffle4Wxyz() => SimdUtils.Shuffle4(this.source4, this.destination4); + + /// + /// Executes the WZYX four-to-four operator. + /// + [Benchmark] + public void Shuffle4Wzyx() => SimdUtils.Shuffle4(this.source4, this.destination4); + + /// + /// Executes the YZWX four-to-four operator. + /// + [Benchmark] + public void Shuffle4Yzwx() => SimdUtils.Shuffle4(this.source4, this.destination4); + + /// + /// Executes the ZYXW four-to-four operator. + /// + [Benchmark] + public void Shuffle4Zyxw() => SimdUtils.Shuffle4(this.source4, this.destination4); + + /// + /// Executes the XWZY four-to-four operator. + /// + [Benchmark] + public void Shuffle4Xwzy() => SimdUtils.Shuffle4(this.source4, this.destination4); + + /// + /// Executes the XYZ four-to-three operator. + /// + [Benchmark] + public void Slice3Xyz() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); + + /// + /// Executes the YZW four-to-three operator. + /// + [Benchmark] + public void Slice3Yzw() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); + + /// + /// Executes the WZY four-to-three operator. + /// + [Benchmark] + public void Slice3Wzy() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); + + /// + /// Executes the ZYX four-to-three operator. + /// + [Benchmark] + public void Slice3Zyx() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); + + /// + /// Executes the XYZW three-to-four operator. + /// + [Benchmark] + public void Pad4Xyzw() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); + + /// + /// Executes the WXYZ three-to-four operator. + /// + [Benchmark] + public void Pad4Wxyz() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); + + /// + /// Executes the WZYX three-to-four operator. + /// + [Benchmark] + public void Pad4Wzyx() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); + + /// + /// Executes the ZYXW three-to-four operator. + /// + [Benchmark] + public void Pad4Zyxw() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); + + /// + /// Executes the ZYX three-to-three operator. + /// + [Benchmark] + public void Shuffle3Zyx() => SimdUtils.Shuffle3(this.source3, this.destination3); +} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index 12b4c3426..4bf1a0b66 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -303,50 +303,30 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4 to the generic utils method. - WXYZShuffle4 wxyz = default; TestShuffleByte4Channel( size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz), + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle2103); - WZYXShuffle4 wzyx = default; TestShuffleByte4Channel( size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx), + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - YZWXShuffle4 yzwx = default; TestShuffleByte4Channel( size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx), + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0321); - ZYXWShuffle4 zyxw = default; TestShuffleByte4Channel( size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw), + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); - DefaultShuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); TestShuffleByte4Channel( size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); - - DefaultShuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); - - DefaultShuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww), - wwww.Control); + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle1230); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -363,32 +343,10 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle3 to the generic utils method. - DefaultShuffle3 zyx = new(SimdUtils.Shuffle.MMShuffle3012); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx), - zyx.Control); - - DefaultShuffle3 xyz = new(SimdUtils.Shuffle.MMShuffle3210); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz), - xyz.Control); - - DefaultShuffle3 yyy = new(SimdUtils.Shuffle.MMShuffle3111); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy), - yyy.Control); - - DefaultShuffle3 zzz = new(SimdUtils.Shuffle.MMShuffle3222); TestShuffleByte3Channel( size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz), - zzz.Control); + (s, d) => SimdUtils.Shuffle3(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -405,32 +363,25 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IPad3Shuffle4 to the generic utils method. - XYZWPad3Shuffle4 xyzw = default; TestPad3Shuffle4Channel( size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw), + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - DefaultPad3Shuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); TestPad3Shuffle4Channel( size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle2103); - DefaultPad3Shuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); TestPad3Shuffle4Channel( size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle0123); - DefaultPad3Shuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); TestPad3Shuffle4Channel( size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww), - wwww.Control); + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -447,32 +398,25 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4Slice3 to the generic utils method. - XYZWShuffle4Slice3 xyzw = default; TestShuffle4Slice3Channel( size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw), + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - DefaultShuffle4Slice3 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); TestShuffle4Slice3Channel( size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz), - xwyz.Control); + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle0321); - DefaultShuffle4Slice3 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); TestShuffle4Slice3Channel( size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy), - yyyy.Control); + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle0123); - DefaultShuffle4Slice3 wwww = new(SimdUtils.Shuffle.MMShuffle3333); TestShuffle4Slice3Channel( size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww), - wwww.Control); + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), + SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( From 501afd48c1fb8f10bac707588c82f35861991580 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 08:16:07 +1000 Subject: [PATCH 224/240] Normalize stateful Vector4 transforms --- .../Utils/Vector4Converters.Affine.cs | 113 ++++------ .../Vector4Converters.AffineOperators.cs | 151 +++++++++++++ .../PixelConversion/Vector4AffineTransform.cs | 207 ++++++++++++++++++ .../Vector4AffineTransformAssembly.cs | 73 ++++++ .../PixelFormats/Vector4ConvertersTests.cs | 122 +++++++++++ 5 files changed, 592 insertions(+), 74 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs create mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs create mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs index 0096085e9..fba3b8fcd 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs @@ -17,59 +17,7 @@ internal static partial class Vector4Converters /// 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; - } - } + => Apply(vectors, new MultiplyThenAddOperator(multiplier, offset)); /// /// Adds the corresponding offset component and then divides each vector component by its divisor. @@ -78,57 +26,74 @@ internal static partial class Vector4Converters /// The component-wise offset applied before division. /// The component-wise divisor. internal static void AddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) + => Apply(vectors, new AddThenDivideOperator(offset, divisor)); + + /// + /// Applies a stateful component transform to a vector buffer in place. + /// + /// The transform selected for this closed traversal. + /// The vectors to transform. + /// The transform and its component-wise state. + // Closing and inlining the traversal lets the JIT devirtualize every Invoke call, + // specialize the active hardware-width branches, and discard unused operator state. + [MethodImpl(InliningOptions.AlwaysInline)] + private static void Apply(Span vectors, TOperator transform) + where TOperator : struct, IStatefulVector4Operator { ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); int index = 0; + // A Vector4 is one complete pixel. Descending register widths therefore consume groups + // of four, two, and one pixels without splitting a pixel across traversal boundaries. 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); + int vectorsPerRegister = Vector512.Count / Vector128.Count; + int oneRegisterFromEnd = vectors.Length - vectorsPerRegister; - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + for (; index <= oneRegisterFromEnd; index += vectorsPerRegister) { - ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); - vector = (vector + offset512) / divisor512; + ref Vector512 vector = ref Unsafe.As>( + ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); } } 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()); + int vectorsPerRegister = Vector256.Count / Vector128.Count; + int oneRegisterFromEnd = vectors.Length - vectorsPerRegister; - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) + for (; index <= oneRegisterFromEnd; index += vectorsPerRegister) { - ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); - vector = (vector + offset256) / divisor256; + ref Vector256 vector = ref Unsafe.As>( + ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); } } if (Vector128.IsHardwareAccelerated) { - Vector128 offset128 = offset.AsVector128(); - Vector128 divisor128 = divisor.AsVector128(); - + // Vector128 and Vector4 have the same four-lane layout, so this stage + // consumes every remaining complete pixel and leaves no scalar remainder. for (; index < vectors.Length; index++) { - ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); - vector = (vector + offset128) / divisor128; + ref Vector128 vector = ref Unsafe.As>( + ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); } return; } - // Native-to-scaled conversion deliberately adds before dividing to match each format's scalar conversion order. + // Vector4 retains the same component order and expression ordering when hardware + // intrinsics are unavailable, preserving the format-specific conversion contract. for (; index < vectors.Length; index++) { ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); - vector = (vector + offset) / divisor; + vector = transform.Invoke(vector); } } } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs new file mode 100644 index 000000000..f5e57f765 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs @@ -0,0 +1,151 @@ +// 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.Utils; + +internal static partial class Vector4Converters +{ + /// + /// Defines a stateful component transform for each register width used by the shared traversal. + /// + private interface IStatefulVector4Operator + { + /// + /// Transforms one pixel represented by four components. + /// + /// The source components. + /// The transformed components. + Vector4 Invoke(Vector4 source); + + /// + /// Transforms one pixel represented by the four single-precision lanes in a 128-bit register. + /// + /// The source components. + /// The transformed components. + Vector128 Invoke(Vector128 source); + + /// + /// Transforms two pixels represented by the eight single-precision lanes in a 256-bit register. + /// + /// The source components. + /// The transformed components. + Vector256 Invoke(Vector256 source); + + /// + /// Transforms four pixels represented by the sixteen single-precision lanes in a 512-bit register. + /// + /// The source components. + /// The transformed components. + Vector512 Invoke(Vector512 source); + } + + /// + /// Carries the component state for a multiply-then-add transform. + /// + private readonly struct MultiplyThenAddOperator : IStatefulVector4Operator + { + private readonly Vector512 multiplier; + private readonly Vector512 offset; + + /// + /// Initializes a new instance of the struct. + /// + /// The component-wise multiplier. + /// The component-wise offset applied after multiplication. + public MultiplyThenAddOperator(Vector4 multiplier, Vector4 offset) + { + Vector128 multiplier128 = multiplier.AsVector128(); + Vector128 offset128 = offset.AsVector128(); + Vector256 multiplier256 = Vector256.Create(multiplier128, multiplier128); + Vector256 offset256 = Vector256.Create(offset128, offset128); + + // Expanding the invariant state once prevents the width-specific Invoke methods + // from rebuilding identical lane groups for every vector processed by the loop. + this.multiplier = Vector512.Create(multiplier256, multiplier256); + this.offset = Vector512.Create(offset256, offset256); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Invoke(Vector4 source) + { + Vector128 result = + (source.AsVector128() * this.multiplier.GetLower().GetLower()) + + this.offset.GetLower().GetLower(); + + return result.AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector128 Invoke(Vector128 source) + => (source * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector256 Invoke(Vector256 source) + => (source * this.multiplier.GetLower()) + this.offset.GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector512 Invoke(Vector512 source) + => (source * this.multiplier) + this.offset; + } + + /// + /// Carries the component state for an add-then-divide transform. + /// + private readonly struct AddThenDivideOperator : IStatefulVector4Operator + { + private readonly Vector512 offset; + private readonly Vector512 divisor; + + /// + /// Initializes a new instance of the struct. + /// + /// The component-wise offset applied before division. + /// The component-wise divisor. + public AddThenDivideOperator(Vector4 offset, Vector4 divisor) + { + Vector128 offset128 = offset.AsVector128(); + Vector128 divisor128 = divisor.AsVector128(); + Vector256 offset256 = Vector256.Create(offset128, offset128); + Vector256 divisor256 = Vector256.Create(divisor128, divisor128); + + // All register widths consume prefixes of this repeated four-pixel state, + // so one construction serves the wide loop and every narrower remainder. + this.offset = Vector512.Create(offset256, offset256); + this.divisor = Vector512.Create(divisor256, divisor256); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Invoke(Vector4 source) + { + Vector128 result = + (source.AsVector128() + this.offset.GetLower().GetLower()) + / this.divisor.GetLower().GetLower(); + + return result.AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector128 Invoke(Vector128 source) + => (source + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector256 Invoke(Vector256 source) + => (source + this.offset.GetLower()) / this.divisor.GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector512 Invoke(Vector512 source) + => (source + this.offset) / this.divisor; + } +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs new file mode 100644 index 000000000..0eb7b9ccd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs @@ -0,0 +1,207 @@ +// 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 BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Compares operator-driven affine vector transforms with the duplicated traversals they replace. +/// +[Config(typeof(Config.Short))] +public class Vector4AffineTransform +{ + private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); + private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); + private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); + + private Vector4[] current; + private Vector4[] baseline; + + /// + /// Gets or sets the number of vectors transformed by each invocation. + /// + [Params(1, 3, 4, 17, 256, 4096)] + public int Count { get; set; } + + /// + /// Creates identical non-uniform buffers for the current and baseline traversals. + /// + [GlobalSetup] + public void Setup() + { + this.current = new Vector4[this.Count]; + + for (int i = 0; i < this.current.Length; i++) + { + this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); + } + + this.baseline = [.. this.current]; + } + + /// + /// Executes the operator-driven multiply-then-add traversal. + /// + [Benchmark] + public void CurrentMultiplyThenAdd() + => Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset); + + /// + /// Executes the duplicated multiply-then-add traversal. + /// + [Benchmark(Baseline = true)] + public void BaselineMultiplyThenAdd() + => BaselineMultiplyThenAdd(this.baseline, Multiplier, Offset); + + /// + /// Executes the operator-driven add-then-divide traversal. + /// + [Benchmark] + public void CurrentAddThenDivide() + => Vector4Converters.AddThenDivide(this.current, Offset, Divisor); + + /// + /// Executes the duplicated add-then-divide traversal. + /// + [Benchmark] + public void BaselineAddThenDivide() + => BaselineAddThenDivide(this.baseline, Offset, Divisor); + + /// + /// Retains the multiply-then-add traversal being replaced for direct measurement. + /// + /// The vectors to transform. + /// The component-wise multiplier. + /// The component-wise offset. + internal static void BaselineMultiplyThenAdd(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; + } + + for (; index < vectors.Length; index++) + { + ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); + vector = (vector * multiplier) + offset; + } + } + + /// + /// Retains the add-then-divide traversal being replaced for direct measurement. + /// + /// The vectors to transform. + /// The component-wise offset. + /// The component-wise divisor. + internal static void BaselineAddThenDivide(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; + } + + for (; index < vectors.Length; index++) + { + ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); + vector = (vector + offset) / divisor; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs new file mode 100644 index 000000000..fcecf1306 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Exposes every stateful affine operator and traversal remainder for assembly inspection. +/// +[Config(typeof(Config.Analysis))] +public class Vector4AffineTransformAssembly +{ + private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); + private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); + private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); + + private Vector4[] vectors; + + /// + /// Gets or sets the number of vectors transformed by each invocation. + /// + /// + /// Three vectors exercise the 256- and 128-bit stages. Seventeen vectors exercise + /// the 512-bit loop and leave one vector for the 128-bit remainder. + /// + [Params(3, 17)] + public int Count { get; set; } + + /// + /// Creates a non-uniform input buffer. + /// + [GlobalSetup] + public void Setup() + { + this.vectors = new Vector4[this.Count]; + + for (int i = 0; i < this.vectors.Length; i++) + { + this.vectors[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); + } + } + + /// + /// Executes the multiply-then-add stateful operator. + /// + [Benchmark] + public void MultiplyThenAdd() + => Vector4Converters.MultiplyThenAdd(this.vectors, Multiplier, Offset); + + /// + /// Executes the add-then-divide stateful operator. + /// + [Benchmark] + public void AddThenDivide() + => Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor); + + /// + /// Executes the multiply-then-add traversal being replaced for assembly comparison. + /// + [Benchmark] + public void BaselineMultiplyThenAdd() + => Vector4AffineTransform.BaselineMultiplyThenAdd(this.vectors, Multiplier, Offset); + + /// + /// Executes the add-then-divide traversal being replaced for assembly comparison. + /// + [Benchmark] + public void BaselineAddThenDivide() + => Vector4AffineTransform.BaselineAddThenDivide(this.vectors, Offset, Divisor); +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs new file mode 100644 index 000000000..52f45aad7 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies the shared stateful traversal used by affine pixel-vector conversions. +/// +[Trait("Category", "PixelFormats")] +public class Vector4ConvertersTests +{ + private static readonly int[] Lengths = [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 257]; + + /// + /// Verifies multiply-then-add behavior for every SIMD boundary and the software fallback. + /// + [Fact] + public void MultiplyThenAddMatchesComponentArithmeticAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertMultiplyThenAddMatchesComponentArithmetic, + HwIntrinsics.AllowAll + | HwIntrinsics.DisableAVX512F + | HwIntrinsics.DisableAVX + | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies add-then-divide behavior for every SIMD boundary and the software fallback. + /// + [Fact] + public void AddThenDivideMatchesComponentArithmeticAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertAddThenDivideMatchesComponentArithmetic, + HwIntrinsics.AllowAll + | HwIntrinsics.DisableAVX512F + | HwIntrinsics.DisableAVX + | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Compares the multiply-then-add traversal with independently evaluated component expressions. + /// + private static void AssertMultiplyThenAddMatchesComponentArithmetic() + { + Vector4 multiplier = new(2F, -3F, .5F, 4F); + Vector4 offset = new(-7F, 11F, 13F, -17F); + + foreach (int length in Lengths) + { + Vector4[] actual = CreateSource(length); + Vector4[] expected = new Vector4[length]; + + for (int i = 0; i < expected.Length; i++) + { + Vector4 value = actual[i]; + + expected[i] = new Vector4( + (value.X * multiplier.X) + offset.X, + (value.Y * multiplier.Y) + offset.Y, + (value.Z * multiplier.Z) + offset.Z, + (value.W * multiplier.W) + offset.W); + } + + Vector4Converters.MultiplyThenAdd(actual, multiplier, offset); + + Assert.Equal(expected, actual); + } + } + + /// + /// Compares the add-then-divide traversal with independently evaluated component expressions. + /// + private static void AssertAddThenDivideMatchesComponentArithmetic() + { + Vector4 offset = new(-7F, 11F, 13F, -17F); + Vector4 divisor = new(2F, -3F, .5F, 4F); + + foreach (int length in Lengths) + { + Vector4[] actual = CreateSource(length); + Vector4[] expected = new Vector4[length]; + + for (int i = 0; i < expected.Length; i++) + { + Vector4 value = actual[i]; + + expected[i] = new Vector4( + (value.X + offset.X) / divisor.X, + (value.Y + offset.Y) / divisor.Y, + (value.Z + offset.Z) / divisor.Z, + (value.W + offset.W) / divisor.W); + } + + Vector4Converters.AddThenDivide(actual, offset, divisor); + + Assert.Equal(expected, actual); + } + } + + /// + /// Creates non-uniform values that expose component ordering and traversal overlap errors. + /// + /// The number of vectors to create. + /// The populated vector buffer. + private static Vector4[] CreateSource(int length) + { + Vector4[] result = new Vector4[length]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = new Vector4( + (i * 17F) - 31F, + (i * -23F) + 37F, + (i * .25F) - 41F, + (i * 3F) + 43F); + } + + return result; + } +} From 9d1aa66f67ae9862948803d4fdfa4284d1ffd993 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 09:24:47 +1000 Subject: [PATCH 225/240] Normalize PNG filter encoding --- .../Formats/Png/Filters/AverageFilter.cs | 98 +--- .../Formats/Png/Filters/IPngFilterOperator.cs | 524 ++++++++++++++++++ .../Formats/Png/Filters/PaethFilter.cs | 153 +---- .../Formats/Png/Filters/PngFilterEncoder.cs | 228 ++++++++ .../Formats/Png/Filters/SubFilter.cs | 81 +-- .../Formats/Png/Filters/UpFilter.cs | 75 +-- .../Codecs/Png/PngFilterEncode.cs | 157 ++++++ .../Codecs/Png/PngFilterEncodeAssembly.cs | 104 ++++ .../Codecs/Png/PngFilterEncodeBaseline.cs | 470 ++++++++++++++++ .../Formats/Png/PngEncoderFilterTests.cs | 134 +++++ 10 files changed, 1642 insertions(+), 382 deletions(-) create mode 100644 src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs create mode 100644 src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 57c202918..942a80ab7 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -140,98 +140,12 @@ internal static class AverageFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, uint bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - resultBaseRef = (byte)FilterType.Average; - - nuint x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet); - Vector256 res = Avx2.Subtract(scan, avg); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else if (Sse2.IsSupported) - { - Vector128 zero = Vector128.Zero; - Vector128 sumAccumulator = Vector128.Zero; - Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count) - { - Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet); - Vector128 res = Sse2.Subtract(scan, avg); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector128.Count; - - Vector128 absRes; - if (Ssse3.IsSupported) - { - absRes = Ssse3.Abs(res.AsSByte()); - } - else - { - Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), res.AsSByte()); - absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask).AsByte(); - } - - sumAccumulator = Sse2.Add(sumAccumulator, Sse2.SumAbsoluteDifferences(absRes, zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(sumAccumulator); - } - - for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - Average(left, above)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } + => PngFilterEncoder.Encode( + scanline, + previousScanline, + result, + bytesPerPixel, + out sum); /// /// Calculates the average value of two bytes diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs new file mode 100644 index 000000000..1113a5df6 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs @@ -0,0 +1,524 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// Defines the scalar and SIMD mappings used by the shared PNG filter traversal. +/// +internal interface IPngFilterOperator +{ + /// + /// Gets the filter type written to the leading result byte. + /// + static abstract FilterType Type { get; } + + /// + /// Gets a value indicating whether the predictor reads the left component. + /// + static abstract bool UsesLeft { get; } + + /// + /// Gets a value indicating whether the predictor reads the above component. + /// + static abstract bool UsesAbove { get; } + + /// + /// Gets a value indicating whether the predictor reads the upper-left component. + /// + static abstract bool UsesUpperLeft { get; } + + /// + /// Filters one byte from its PNG neighborhood. + /// + /// The component being filtered. + /// The corresponding component in the preceding pixel. + /// The corresponding component in the preceding scanline. + /// The preceding component in the preceding scanline. + /// The filtered residual. + static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft); + + /// + /// Filters sixteen byte lanes from their PNG neighborhoods. + /// + /// The components being filtered. + /// The corresponding components in the preceding pixels. + /// The corresponding components in the preceding scanline. + /// The preceding components in the preceding scanline. + /// The filtered residuals. + static abstract Vector128 Invoke( + Vector128 scan, + Vector128 left, + Vector128 above, + Vector128 upperLeft); + + /// + /// Filters thirty-two byte lanes from their PNG neighborhoods. + /// + /// The components being filtered. + /// The corresponding components in the preceding pixels. + /// The corresponding components in the preceding scanline. + /// The preceding components in the preceding scanline. + /// The filtered residuals. + static abstract Vector256 Invoke( + Vector256 scan, + Vector256 left, + Vector256 above, + Vector256 upperLeft); + + /// + /// Filters sixty-four byte lanes from their PNG neighborhoods. + /// + /// The components being filtered. + /// The corresponding components in the preceding pixels. + /// The corresponding components in the preceding scanline. + /// The preceding components in the preceding scanline. + /// The filtered residuals. + static abstract Vector512 Invoke( + Vector512 scan, + Vector512 left, + Vector512 above, + Vector512 upperLeft); +} + +/// +/// Maps each component to its difference from the corresponding component in the preceding pixel. +/// +internal readonly struct SubFilterOperator : IPngFilterOperator +{ + /// + public static FilterType Type => FilterType.Sub; + + /// + public static bool UsesLeft => true; + + /// + public static bool UsesAbove => false; + + /// + public static bool UsesUpperLeft => false; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - left); + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector128 Invoke( + Vector128 scan, + Vector128 left, + Vector128 above, + Vector128 upperLeft) + => scan - left; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector256 Invoke( + Vector256 scan, + Vector256 left, + Vector256 above, + Vector256 upperLeft) + => scan - left; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector512 Invoke( + Vector512 scan, + Vector512 left, + Vector512 above, + Vector512 upperLeft) + => scan - left; +} + +/// +/// Maps each component to its difference from the component directly above it. +/// +internal readonly struct UpFilterOperator : IPngFilterOperator +{ + /// + public static FilterType Type => FilterType.Up; + + /// + public static bool UsesLeft => false; + + /// + public static bool UsesAbove => true; + + /// + public static bool UsesUpperLeft => false; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) => (byte)(scan - above); + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector128 Invoke( + Vector128 scan, + Vector128 left, + Vector128 above, + Vector128 upperLeft) + => scan - above; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector256 Invoke( + Vector256 scan, + Vector256 left, + Vector256 above, + Vector256 upperLeft) + => scan - above; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector512 Invoke( + Vector512 scan, + Vector512 left, + Vector512 above, + Vector512 upperLeft) + => scan - above; +} + +/// +/// Maps each component to its difference from the truncated average of its left and above neighbors. +/// +internal readonly struct AverageFilterOperator : IPngFilterOperator +{ + /// + public static FilterType Type => FilterType.Average; + + /// + public static bool UsesLeft => true; + + /// + public static bool UsesAbove => true; + + /// + public static bool UsesUpperLeft => false; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) + => (byte)(scan - ((left + above) >> 1)); + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector128 Invoke( + Vector128 scan, + Vector128 left, + Vector128 above, + Vector128 upperLeft) + { + Vector128 average; + + if (Sse2.IsSupported) + { + // PAVG rounds upward. Complementing both inputs and the result converts + // that rounding into the floor((left + above) / 2) required by PNG. + average = ~Sse2.Average(~left, ~above); + } + else if (AdvSimd.IsSupported) + { + // ARM's halving add truncates directly and therefore needs no correction. + average = AdvSimd.FusedAddHalving(left, above); + } + else + { + // Portable 128-bit backends use the carry-free average identity. Shared + // bits supply the integer part while differing bits supply half the remainder. + average = (left & above) + Vector128.ShiftRightLogical(left ^ above, 1); + } + + return scan - average; + } + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector256 Invoke( + Vector256 scan, + Vector256 left, + Vector256 above, + Vector256 upperLeft) + => scan - ~Avx2.Average(~left, ~above); + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector512 Invoke( + Vector512 scan, + Vector512 left, + Vector512 above, + Vector512 upperLeft) + => scan - ~Avx512BW.Average(~left, ~above); +} + +/// +/// Maps each component to its difference from the nearest Paeth neighbor. +/// +internal readonly struct PaethFilterOperator : IPngFilterOperator +{ + /// + public static FilterType Type => FilterType.Paeth; + + /// + public static bool UsesLeft => true; + + /// + public static bool UsesAbove => true; + + /// + public static bool UsesUpperLeft => true; + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static byte Invoke(byte scan, byte left, byte above, byte upperLeft) + { + int p = left + above - upperLeft; + int distanceLeft = Numerics.Abs(p - left); + int distanceAbove = Numerics.Abs(p - above); + int distanceUpperLeft = Numerics.Abs(p - upperLeft); + + // PNG resolves equal distances in left, above, upper-left order. + byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft + ? left + : distanceAbove <= distanceUpperLeft ? above : upperLeft; + + return (byte)(scan - predictor); + } + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector128 Invoke( + Vector128 scan, + Vector128 left, + Vector128 above, + Vector128 upperLeft) + { + Vector128 predictor = Predict(left, above, upperLeft); + return scan - predictor; + } + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector256 Invoke( + Vector256 scan, + Vector256 left, + Vector256 above, + Vector256 upperLeft) + { + Vector256 predictor = Predict(left, above, upperLeft); + return scan - predictor; + } + + /// + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector512 Invoke( + Vector512 scan, + Vector512 left, + Vector512 above, + Vector512 upperLeft) + { + Vector512 predictor = Predict(left, above, upperLeft); + return scan - predictor; + } + + /// + /// Selects the nearest Paeth neighbor for sixteen independent byte lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector128 Predict( + Vector128 left, + Vector128 above, + Vector128 upperLeft) + { + Vector128 aboveMinusUpper = SubtractSaturate(above, upperLeft); + Vector128 leftMinusUpper = SubtractSaturate(left, upperLeft); + Vector128 distanceLeft = SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector128 distanceAbove = SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor( + left, + above, + upperLeft, + aboveMinusUpper, + leftMinusUpper, + distanceLeft, + distanceAbove); + } + + /// + /// Selects the nearest Paeth neighbor for thirty-two independent byte lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector256 Predict( + Vector256 left, + Vector256 above, + Vector256 upperLeft) + { + Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); + Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); + Vector256 distanceLeft = Avx2.SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector256 distanceAbove = Avx2.SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor( + left, + above, + upperLeft, + aboveMinusUpper, + leftMinusUpper, + distanceLeft, + distanceAbove); + } + + /// + /// Selects the nearest Paeth neighbor for sixty-four independent byte lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector512 Predict( + Vector512 left, + Vector512 above, + Vector512 upperLeft) + { + Vector512 aboveMinusUpper = Avx512BW.SubtractSaturate(above, upperLeft); + Vector512 leftMinusUpper = Avx512BW.SubtractSaturate(left, upperLeft); + Vector512 distanceLeft = Avx512BW.SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector512 distanceAbove = Avx512BW.SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor( + left, + above, + upperLeft, + aboveMinusUpper, + leftMinusUpper, + distanceLeft, + distanceAbove); + } + + /// + /// Applies Paeth distance and tie-breaking rules to sixteen lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector128 SelectPredictor( + Vector128 left, + Vector128 above, + Vector128 upperLeft, + Vector128 aboveMinusUpper, + Vector128 leftMinusUpper, + Vector128 distanceLeft, + Vector128 distanceAbove) + { + Vector128 sameDirection = Vector128.Equals( + Vector128.Equals(aboveMinusUpper, Vector128.Zero), + Vector128.Equals(leftMinusUpper, Vector128.Zero)); + + Vector128 distanceUpper = sameDirection + | SubtractSaturate(distanceAbove, distanceLeft) + | SubtractSaturate(distanceLeft, distanceAbove); + + Vector128 minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove); + Vector128 aboveOrUpper = Vector128.ConditionalSelect( + Vector128.Equals(minimumAboveUpper, distanceAbove), + above, + upperLeft); + + // Applying the left comparison last preserves PNG's left-first tie rule. + return Vector128.ConditionalSelect( + Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft), + left, + aboveOrUpper); + } + + /// + /// Applies Paeth distance and tie-breaking rules to thirty-two lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector256 SelectPredictor( + Vector256 left, + Vector256 above, + Vector256 upperLeft, + Vector256 aboveMinusUpper, + Vector256 leftMinusUpper, + Vector256 distanceLeft, + Vector256 distanceAbove) + { + Vector256 sameDirection = Vector256.Equals( + Vector256.Equals(aboveMinusUpper, Vector256.Zero), + Vector256.Equals(leftMinusUpper, Vector256.Zero)); + + Vector256 distanceUpper = sameDirection + | Avx2.SubtractSaturate(distanceAbove, distanceLeft) + | Avx2.SubtractSaturate(distanceLeft, distanceAbove); + + Vector256 minimumAboveUpper = Vector256.Min(distanceUpper, distanceAbove); + Vector256 aboveOrUpper = Vector256.ConditionalSelect( + Vector256.Equals(minimumAboveUpper, distanceAbove), + above, + upperLeft); + + return Vector256.ConditionalSelect( + Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft), + left, + aboveOrUpper); + } + + /// + /// Applies Paeth distance and tie-breaking rules to sixty-four lanes. + /// + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector512 SelectPredictor( + Vector512 left, + Vector512 above, + Vector512 upperLeft, + Vector512 aboveMinusUpper, + Vector512 leftMinusUpper, + Vector512 distanceLeft, + Vector512 distanceAbove) + { + Vector512 sameDirection = Vector512.Equals( + Vector512.Equals(aboveMinusUpper, Vector512.Zero), + Vector512.Equals(leftMinusUpper, Vector512.Zero)); + + Vector512 distanceUpper = sameDirection + | Avx512BW.SubtractSaturate(distanceAbove, distanceLeft) + | Avx512BW.SubtractSaturate(distanceLeft, distanceAbove); + + Vector512 minimumAboveUpper = Vector512.Min(distanceUpper, distanceAbove); + Vector512 aboveOrUpper = Vector512.ConditionalSelect( + Vector512.Equals(minimumAboveUpper, distanceAbove), + above, + upperLeft); + + return Vector512.ConditionalSelect( + Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft), + left, + aboveOrUpper); + } + + /// + /// Performs an unsigned saturating subtraction using the active 128-bit instruction set. + /// + /// The minuend lanes. + /// The subtrahend lanes. + /// The saturated lane-wise differences. + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector128 SubtractSaturate(Vector128 left, Vector128 right) + { + if (Sse2.IsSupported) + { + return Sse2.SubtractSaturate(left, right); + } + + if (AdvSimd.IsSupported) + { + return AdvSimd.SubtractSaturate(left, right); + } + + // Subtracting the smaller operand produces max(left - right, 0) without + // requiring a backend-specific saturating-subtract instruction. + return left - Vector128.Min(left, right); + } +} diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 59c903c1d..0216a2627 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -1,7 +1,6 @@ // 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; @@ -193,86 +192,12 @@ internal static class PaethFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - resultBaseRef = (byte)FilterType.Paeth; - - nuint x = 0; - for (; x < (uint)bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(0, above, 0)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - - Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - - Vector res = scan - PaethPredictor(left, above, upperLeft); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; - - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } + => PngFilterEncoder.Encode( + scanline, + previousScanline, + result, + (uint)bytesPerPixel, + out sum); /// /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses @@ -304,70 +229,4 @@ internal static class PaethFilter return upperLeft; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft) - { - Vector256 zero = Vector256.Zero; - - // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left) - // to pa = abs(above - upleft). Same deal for pb. - // Using saturated subtraction, if the result is negative, the output is zero. - // If we subtract in both directions and `or` the results, only one can be - // non-zero, so we end up with the absolute value. - Vector256 sac = Avx2.SubtractSaturate(above, upleft); - Vector256 sbc = Avx2.SubtractSaturate(left, upleft); - Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac); - Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc); - - // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft). - // We've already calculated left - upleft and above - upleft in `sac` and `sbc`. - // If they are both negative or both positive, the absolute value of their - // sum can't possibly be less than `pa` or `pb`, so we'll never use the value. - // We make a mask that sets the value to 255 if they either both got - // saturated to zero or both didn't. Then we calculate the absolute value - // of their difference using saturated subtract and `or`, same as before, - // keeping the value only where the mask isn't set. - Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero)); - Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb))); - - // Finally, blend the values together. We start with `upleft` and overwrite on - // tied values so that the `left`, `above`, `upleft` precedence is preserved. - Vector256 minbc = Avx2.Min(pc, pb); - Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb)); - return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) - { - Vector.Widen(left, out Vector a1, out Vector a2); - Vector.Widen(above, out Vector b1, out Vector b2); - Vector.Widen(upperLeft, out Vector c1, out Vector c2); - - Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1)); - Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2)); - return Vector.AsVectorByte(Vector.Narrow(p1, p2)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) - { - Vector p = left + above - upperLeft; - Vector pa = Vector.Abs(p - left); - Vector pb = Vector.Abs(p - above); - Vector pc = Vector.Abs(p - upperLeft); - - Vector pa_pb = Vector.LessThanOrEqual(pa, pb); - Vector pa_pc = Vector.LessThanOrEqual(pa, pc); - Vector pb_pc = Vector.LessThanOrEqual(pb, pc); - - return Vector.ConditionalSelect( - condition: Vector.BitwiseAnd(pa_pb, pa_pc), - left: left, - right: Vector.ConditionalSelect( - condition: pb_pc, - left: above, - right: upperLeft)); - } } diff --git a/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs b/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs new file mode 100644 index 000000000..4c298e0fc --- /dev/null +++ b/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// Applies PNG filter operators while accumulating the absolute signed residuals used for filter selection. +/// +internal static class PngFilterEncoder +{ + /// + /// Maps a scanline through a filter operator, writes the residuals, and reduces their total variance. + /// + /// The PNG predictor selected for this closed traversal. + /// The scanline to encode. + /// The preceding scanline. + /// The destination including its leading filter byte. + /// The distance to the corresponding component in the preceding pixel. + /// The sum of the absolute signed residuals. + // Inlining closes every static interface call over TOperator. The JIT can then remove + // source loads ignored by simpler predictors and specialize the active register widths. + [MethodImpl(InliningOptions.AlwaysInline)] + public static void Encode( + ReadOnlySpan scanline, + ReadOnlySpan previousScanline, + Span result, + uint bytesPerPixel, + out int sum) + where TOperator : struct, IPngFilterOperator + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + + resultBaseRef = (byte)TOperator.Type; + sum = 0; + + nuint x = 0; + + // Components in the first pixel have no left or upper-left neighbor. Supplying + // zeroes expresses the PNG boundary rule directly through the same predictor. + for (; x < bytesPerPixel; x++) + { + byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; + + byte filtered = TOperator.Invoke( + Unsafe.Add(ref scanBaseRef, x), + 0, + above, + 0); + + Unsafe.Add(ref resultBaseRef, x + 1) = filtered; + sum += Numerics.Abs(unchecked((sbyte)filtered)); + } + + Vector128 sum128 = Vector128.Zero; + + // A single 512-bit register does not amortize folding its SAD accumulator. + // Leave short rows to the narrower paths, which have lower fixed reduction cost. + if (Avx512BW.IsSupported && scanline.Length - (int)x >= Vector512.Count * 2) + { + Vector512 sum512 = Vector512.Zero; + int oneRegisterFromEnd = scanline.Length - Vector512.Count; + + for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector512.Count) + { + // Each byte lane represents one independently filtered component. The + // four input vectors retain the scan/left/above/upper-left PNG layout. + // Operator usage flags are constants after generic specialization, so + // unused predictors do not retain even fault-preserving probe loads. + Vector512 left = TOperator.UsesLeft + ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) + : default; + Vector512 above = TOperator.UsesAbove + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) + : default; + Vector512 upperLeft = TOperator.UsesUpperLeft + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) + : default; + + Vector512 filtered = TOperator.Invoke( + Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), + left, + above, + upperLeft); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; + x += (uint)Vector512.Count; + + // PNG scores each residual as abs((sbyte)residual). VPSADBW sums eight + // byte lanes into every other 32-bit lane without widening each byte. + Vector512 absolute = Avx512BW.Abs(filtered.AsSByte()); + sum512 += Avx512BW.SumAbsoluteDifferences(absolute, Vector512.Zero).AsUInt32(); + } + + // Fold only widths that processed data. Short rows therefore avoid both + // wide accumulator initialization and an otherwise empty reduction. + Vector256 folded512 = sum512.GetLower() + sum512.GetUpper(); + sum128 += folded512.GetLower() + folded512.GetUpper(); + } + + if (Avx2.IsSupported) + { + Vector256 sum256 = Vector256.Zero; + int oneRegisterFromEnd = scanline.Length - Vector256.Count; + + for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector256.Count) + { + Vector256 left = TOperator.UsesLeft + ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) + : default; + Vector256 above = TOperator.UsesAbove + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) + : default; + Vector256 upperLeft = TOperator.UsesUpperLeft + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) + : default; + + Vector256 filtered = TOperator.Invoke( + Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), + left, + above, + upperLeft); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; + x += (uint)Vector256.Count; + + Vector256 absolute = Avx2.Abs(filtered.AsSByte()); + sum256 += Avx2.SumAbsoluteDifferences(absolute, Vector256.Zero).AsUInt32(); + } + + sum128 += sum256.GetLower() + sum256.GetUpper(); + } + + if (Vector128.IsHardwareAccelerated) + { + int oneRegisterFromEnd = scanline.Length - Vector128.Count; + + for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector128.Count) + { + Vector128 left = TOperator.UsesLeft + ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) + : default; + Vector128 above = TOperator.UsesAbove + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) + : default; + Vector128 upperLeft = TOperator.UsesUpperLeft + ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) + : default; + + Vector128 filtered = TOperator.Invoke( + Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), + left, + above, + upperLeft); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; + x += (uint)Vector128.Count; + + sum128 = AccumulateAbsolute(sum128, filtered); + } + } + + sum += unchecked((int)Vector128.Sum(sum128)); + + for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++, x++) + { + byte left = TOperator.UsesLeft ? Unsafe.Add(ref scanBaseRef, xLeft) : (byte)0; + byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; + byte upperLeft = TOperator.UsesUpperLeft ? Unsafe.Add(ref previousBaseRef, xLeft) : (byte)0; + + byte filtered = TOperator.Invoke( + Unsafe.Add(ref scanBaseRef, x), + left, + above, + upperLeft); + + Unsafe.Add(ref resultBaseRef, x + 1) = filtered; + sum += Numerics.Abs(unchecked((sbyte)filtered)); + } + } + + /// + /// Accumulates the absolute signed values in a 128-bit residual vector. + /// + /// The four-lane unsigned accumulator. + /// The sixteen filtered byte residuals. + /// The updated accumulator. + [MethodImpl(InliningOptions.AlwaysInline)] + private static Vector128 AccumulateAbsolute(Vector128 accumulator, Vector128 residuals) + { + if (Sse2.IsSupported) + { + Vector128 absolute; + + if (Ssse3.IsSupported) + { + absolute = Ssse3.Abs(residuals.AsSByte()); + } + else + { + // SSE2 has no packed signed-byte absolute instruction. The sign mask + // implements (value + mask) XOR mask, including -128 -> 128. + Vector128 mask = Sse2.CompareGreaterThan(Vector128.Zero, residuals.AsSByte()); + absolute = Sse2.Xor(Sse2.Add(residuals.AsSByte(), mask), mask).AsByte(); + } + + return accumulator + Sse2.SumAbsoluteDifferences(absolute, Vector128.Zero).AsUInt32(); + } + + Vector128 absoluteArm = Vector128.Abs(residuals.AsSByte()).AsByte(); + (Vector128 lower16, Vector128 upper16) = Vector128.Widen(absoluteArm); + (Vector128 lower0, Vector128 lower1) = Vector128.Widen(lower16); + (Vector128 upper0, Vector128 upper1) = Vector128.Widen(upper16); + + // Four widening additions keep every byte contribution in a 32-bit lane, + // matching the x86 accumulator's overflow behavior without scalar reduction. + return accumulator + lower0 + lower1 + upper0 + upper1; + } +} diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 1af4a3b72..957fb670f 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -1,7 +1,6 @@ // 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; @@ -110,77 +109,11 @@ internal static class SubFilter /// The bytes per pixel. /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) - { - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Sub(x) = Raw(x) - Raw(x-bpp) - resultBaseRef = (byte)FilterType.Sub; - - nuint x = 0; - for (; x < (uint)bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = scan; - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= (scanline.Length - Vector256.Count); xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - - Vector256 res = Avx2.Subtract(scan, prev); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else - if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= (scanline.Length - Vector.Count); xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - - Vector res = scan - prev; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; - - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - prev); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } + public static void Encode(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) + => PngFilterEncoder.Encode( + scanline, + scanline, + result, + (uint)bytesPerPixel, + out sum); } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index d9c8e36d6..5f78833cd 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -1,11 +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 System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Formats.Png.Filters; @@ -40,69 +36,10 @@ internal static class UpFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Up(x) = Raw(x) - Prior(x) - resultBaseRef = (byte)FilterType.Up; - - nuint x = 0; - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - - for (; (int)x <= scanline.Length - Vector256.Count;) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector256 res = Avx2.Subtract(scan, above); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector256.Count; - - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - - for (; (int)x <= scanline.Length - Vector.Count;) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector res = scan - above; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += (uint)Vector.Count; - - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } - } - - for (; x < (uint)scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - } + => PngFilterEncoder.Encode( + scanline, + previousScanline, + result, + 0, + out sum); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs new file mode 100644 index 000000000..0547376b7 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Png.Filters; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; + +/// +/// Compares the shared PNG map/reduce traversal with the filter-specific traversals it replaces. +/// +[Config(typeof(Config.Short))] +public class PngFilterEncode +{ + private const int BytesPerPixel = 4; + + private byte[] scanline; + private byte[] previousScanline; + private byte[] currentResult; + private byte[] baselineResult; + + /// + /// Gets or sets the filter evaluated by each invocation. + /// + [Params(PngFilterMethod.Sub, PngFilterMethod.Up, PngFilterMethod.Average, PngFilterMethod.Paeth)] + public PngFilterMethod Filter { get; set; } + + /// + /// Gets or sets the number of scanline bytes. + /// + [Params(64, 1024, 16384)] + public int Count { get; set; } + + /// + /// Creates deterministic non-uniform inputs and independent result buffers. + /// + [GlobalSetup] + public void Setup() + { + this.scanline = new byte[this.Count]; + this.previousScanline = new byte[this.Count]; + this.currentResult = new byte[this.Count + 1]; + this.baselineResult = new byte[this.Count + 1]; + + Random random = new(12345678); + random.NextBytes(this.scanline); + random.NextBytes(this.previousScanline); + } + + /// + /// Executes the operator-driven map/reduce traversal. + /// + /// The filter variance sum. + [Benchmark] + public int Current() + => this.Filter switch + { + PngFilterMethod.Sub => this.EncodeSubCurrent(), + PngFilterMethod.Up => this.EncodeUpCurrent(), + PngFilterMethod.Average => this.EncodeAverageCurrent(), + PngFilterMethod.Paeth => this.EncodePaethCurrent(), + _ => throw new InvalidOperationException() + }; + + /// + /// Executes the filter-specific traversal being replaced. + /// + /// The filter variance sum. + [Benchmark(Baseline = true)] + public int Baseline() + { + int sum; + + switch (this.Filter) + { + case PngFilterMethod.Sub: + PngFilterEncodeBaseline.EncodeSub( + this.scanline, + this.baselineResult, + BytesPerPixel, + out sum); + + break; + + case PngFilterMethod.Up: + PngFilterEncodeBaseline.EncodeUp( + this.scanline, + this.previousScanline, + this.baselineResult, + out sum); + + break; + + case PngFilterMethod.Average: + PngFilterEncodeBaseline.EncodeAverage( + this.scanline, + this.previousScanline, + this.baselineResult, + BytesPerPixel, + out sum); + + break; + + case PngFilterMethod.Paeth: + PngFilterEncodeBaseline.EncodePaeth( + this.scanline, + this.previousScanline, + this.baselineResult, + BytesPerPixel, + out sum); + + break; + + default: + throw new InvalidOperationException(); + } + + return sum; + } + + /// + /// Executes the current Sub encoder. + /// + private int EncodeSubCurrent() + { + SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out int sum); + return sum; + } + + /// + /// Executes the current Up encoder. + /// + private int EncodeUpCurrent() + { + UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out int sum); + return sum; + } + + /// + /// Executes the current Average encoder. + /// + private int EncodeAverageCurrent() + { + AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + return sum; + } + + /// + /// Executes the current Paeth encoder. + /// + private int EncodePaethCurrent() + { + PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + return sum; + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs new file mode 100644 index 000000000..bf8a18c12 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png.Filters; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; + +/// +/// Exposes every normalized PNG filter and retained baseline for assembly comparison. +/// +[Config(typeof(Config.Analysis))] +public class PngFilterEncodeAssembly +{ + private const int BytesPerPixel = 4; + private const int Count = 180; + + private byte[] scanline; + private byte[] previousScanline; + private byte[] currentResult; + private byte[] baselineResult; + + /// + /// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths. + /// + [GlobalSetup] + public void Setup() + { + this.scanline = new byte[Count]; + this.previousScanline = new byte[Count]; + this.currentResult = new byte[Count + 1]; + this.baselineResult = new byte[Count + 1]; + + Random random = new(12345678); + random.NextBytes(this.scanline); + random.NextBytes(this.previousScanline); + } + + /// + /// Executes the normalized Sub encoder. + /// + [Benchmark] + public void Sub() + => SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out _); + + /// + /// Executes the normalized Up encoder. + /// + [Benchmark] + public void Up() + => UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out _); + + /// + /// Executes the normalized Average encoder. + /// + [Benchmark] + public void Average() + => AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); + + /// + /// Executes the normalized Paeth encoder. + /// + [Benchmark] + public void Paeth() + => PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); + + /// + /// Executes the retained Sub encoder. + /// + [Benchmark] + public void BaselineSub() + => PngFilterEncodeBaseline.EncodeSub(this.scanline, this.baselineResult, BytesPerPixel, out _); + + /// + /// Executes the retained Up encoder. + /// + [Benchmark] + public void BaselineUp() + => PngFilterEncodeBaseline.EncodeUp(this.scanline, this.previousScanline, this.baselineResult, out _); + + /// + /// Executes the retained Average encoder. + /// + [Benchmark] + public void BaselineAverage() + => PngFilterEncodeBaseline.EncodeAverage( + this.scanline, + this.previousScanline, + this.baselineResult, + BytesPerPixel, + out _); + + /// + /// Executes the retained Paeth encoder. + /// + [Benchmark] + public void BaselinePaeth() + => PngFilterEncodeBaseline.EncodePaeth( + this.scanline, + this.previousScanline, + this.baselineResult, + BytesPerPixel, + out _); +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs new file mode 100644 index 000000000..e28cc8105 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs @@ -0,0 +1,470 @@ +// 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.Benchmarks.Codecs.Png; + +/// +/// Retains the filter-specific PNG encode traversals for direct performance comparison. +/// +internal static class PngFilterEncodeBaseline +{ + /// + /// Executes the filter-specific Sub traversal. + /// + public static void EncodeSub( + ReadOnlySpan scanline, + Span result, + int bytesPerPixel, + out int sum) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + resultBaseRef = 1; + + nuint x = 0; + + for (; x < (uint)bytesPerPixel;) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = scan; + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 accumulator = Vector256.Zero; + + for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector256 residual = Avx2.Subtract(scan, left); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector256.Count; + accumulator = Avx2.Add( + accumulator, + Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); + } + + sum += Numerics.EvenReduceSum(accumulator); + } + else if (Vector.IsHardwareAccelerated) + { + Vector accumulator = Vector.Zero; + + for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) + { + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector residual = scan - left; + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector.Count; + Numerics.Accumulate( + ref accumulator, + Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); + } + + for (int i = 0; i < Vector.Count; i++) + { + sum += (int)accumulator[i]; + } + } + + for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - left); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + } + + /// + /// Executes the filter-specific Up traversal. + /// + public static void EncodeUp( + ReadOnlySpan scanline, + ReadOnlySpan previousScanline, + Span result, + out int sum) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + resultBaseRef = 2; + + nuint x = 0; + + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 accumulator = Vector256.Zero; + + for (; (int)x <= scanline.Length - Vector256.Count;) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector256 residual = Avx2.Subtract(scan, above); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector256.Count; + accumulator = Avx2.Add( + accumulator, + Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); + } + + sum += Numerics.EvenReduceSum(accumulator); + } + else if (Vector.IsHardwareAccelerated) + { + Vector accumulator = Vector.Zero; + + for (; (int)x <= scanline.Length - Vector.Count;) + { + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector residual = scan - above; + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector.Count; + Numerics.Accumulate( + ref accumulator, + Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); + } + + for (int i = 0; i < Vector.Count; i++) + { + sum += (int)accumulator[i]; + } + } + + for (; x < (uint)scanline.Length;) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref previousBaseRef, x); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - above); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + } + + /// + /// Executes the filter-specific Average traversal. + /// + public static void EncodeAverage( + ReadOnlySpan scanline, + ReadOnlySpan previousScanline, + Span result, + uint bytesPerPixel, + out int sum) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + resultBaseRef = 3; + + nuint x = 0; + + for (; x < bytesPerPixel;) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref previousBaseRef, x); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - (above >> 1)); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 accumulator = Vector256.Zero; + Vector256 allBitsSet = Avx2.CompareEqual(accumulator, accumulator).AsByte(); + + for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector256 average = Avx2.Xor( + Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), + allBitsSet); + + Vector256 residual = Avx2.Subtract(scan, average); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector256.Count; + accumulator = Avx2.Add( + accumulator, + Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); + } + + sum += Numerics.EvenReduceSum(accumulator); + } + else if (Sse2.IsSupported) + { + Vector128 zero = Vector128.Zero; + Vector128 accumulator = Vector128.Zero; + Vector128 allBitsSet = Sse2.CompareEqual(accumulator, accumulator).AsByte(); + + for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count) + { + Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector128 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector128 average = Sse2.Xor( + Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), + allBitsSet); + + Vector128 residual = Sse2.Subtract(scan, average); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector128.Count; + + Vector128 absolute; + + if (Ssse3.IsSupported) + { + absolute = Ssse3.Abs(residual.AsSByte()); + } + else + { + Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), residual.AsSByte()); + absolute = Sse2.Xor(Sse2.Add(residual.AsSByte(), mask), mask).AsByte(); + } + + accumulator = Sse2.Add( + accumulator, + Sse2.SumAbsoluteDifferences(absolute, zero).AsInt32()); + } + + sum += Numerics.EvenReduceSum(accumulator); + } + + for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref previousBaseRef, x); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - ((left + above) >> 1)); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + } + + /// + /// Executes the filter-specific Paeth traversal. + /// + public static void EncodePaeth( + ReadOnlySpan scanline, + ReadOnlySpan previousScanline, + Span result, + int bytesPerPixel, + out int sum) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + resultBaseRef = 4; + + nuint x = 0; + + for (; x < (uint)bytesPerPixel;) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref previousBaseRef, x); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - above); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 accumulator = Vector256.Zero; + + for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); + Vector256 residual = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector256.Count; + accumulator = Avx2.Add( + accumulator, + Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); + } + + sum += Numerics.EvenReduceSum(accumulator); + } + else if (Vector.IsHardwareAccelerated) + { + Vector accumulator = Vector.Zero; + + for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) + { + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); + Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); + Vector residual = scan - PaethPredictor(left, above, upperLeft); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; + x += (uint)Vector.Count; + Numerics.Accumulate( + ref accumulator, + Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); + } + + for (int i = 0; i < Vector.Count; i++) + { + sum += (int)accumulator[i]; + } + } + + for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref previousBaseRef, x); + byte upperLeft = Unsafe.Add(ref previousBaseRef, xLeft); + x++; + ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); + residual = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += Numerics.Abs(unchecked((sbyte)residual)); + } + } + + /// + /// Selects the scalar Paeth predictor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte PaethPredictor(byte left, byte above, byte upperLeft) + { + int p = left + above - upperLeft; + int distanceLeft = Numerics.Abs(p - left); + int distanceAbove = Numerics.Abs(p - above); + int distanceUpperLeft = Numerics.Abs(p - upperLeft); + + if (distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft) + { + return left; + } + + return distanceAbove <= distanceUpperLeft ? above : upperLeft; + } + + /// + /// Selects the AVX2 Paeth predictor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 PaethPredictor( + Vector256 left, + Vector256 above, + Vector256 upperLeft) + { + Vector256 zero = Vector256.Zero; + Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); + Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); + Vector256 distanceLeft = + Avx2.Or(Avx2.SubtractSaturate(upperLeft, above), aboveMinusUpper); + + Vector256 distanceAbove = + Avx2.Or(Avx2.SubtractSaturate(upperLeft, left), leftMinusUpper); + + Vector256 sameDirection = Avx2.CompareEqual( + Avx2.CompareEqual(aboveMinusUpper, zero), + Avx2.CompareEqual(leftMinusUpper, zero)); + + Vector256 distanceUpper = Avx2.Or( + sameDirection, + Avx2.Or( + Avx2.SubtractSaturate(distanceAbove, distanceLeft), + Avx2.SubtractSaturate(distanceLeft, distanceAbove))); + + Vector256 minimumAboveUpper = Avx2.Min(distanceUpper, distanceAbove); + Vector256 aboveOrUpper = Avx2.BlendVariable( + upperLeft, + above, + Avx2.CompareEqual(minimumAboveUpper, distanceAbove)); + + return Avx2.BlendVariable( + aboveOrUpper, + left, + Avx2.CompareEqual(Avx2.Min(minimumAboveUpper, distanceLeft), distanceLeft)); + } + + /// + /// Selects the portable vector Paeth predictor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector PaethPredictor( + Vector left, + Vector above, + Vector upperLeft) + { + Vector.Widen(left, out Vector leftLow, out Vector leftHigh); + Vector.Widen(above, out Vector aboveLow, out Vector aboveHigh); + Vector.Widen(upperLeft, out Vector upperLow, out Vector upperHigh); + + Vector lower = PaethPredictor( + Vector.AsVectorInt16(leftLow), + Vector.AsVectorInt16(aboveLow), + Vector.AsVectorInt16(upperLow)); + + Vector upper = PaethPredictor( + Vector.AsVectorInt16(leftHigh), + Vector.AsVectorInt16(aboveHigh), + Vector.AsVectorInt16(upperHigh)); + + return Vector.AsVectorByte(Vector.Narrow(lower, upper)); + } + + /// + /// Selects the portable widened Paeth predictor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector PaethPredictor( + Vector left, + Vector above, + Vector upperLeft) + { + Vector p = left + above - upperLeft; + Vector distanceLeft = Vector.Abs(p - left); + Vector distanceAbove = Vector.Abs(p - above); + Vector distanceUpper = Vector.Abs(p - upperLeft); + + Vector chooseLeft = Vector.BitwiseAnd( + Vector.LessThanOrEqual(distanceLeft, distanceAbove), + Vector.LessThanOrEqual(distanceLeft, distanceUpper)); + + return Vector.ConditionalSelect( + chooseLeft, + left, + Vector.ConditionalSelect( + Vector.LessThanOrEqual(distanceAbove, distanceUpper), + above, + upperLeft)); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs index 3a31b395f..0e76f4fe9 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs @@ -208,6 +208,140 @@ public class PngEncoderFilterTests : MeasureFixture HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } + /// + /// Verifies every encoder across pixel strides, register boundaries, and hardware widths. + /// + [Fact] + public void EncodeMatchesReferencesAcrossRegisterBoundaries() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertEncodersMatchReferences, + HwIntrinsics.AllowAll + | HwIntrinsics.DisableAVX512F + | HwIntrinsics.DisableAVX2 + | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Compares every filter with its independent scalar reference across SIMD boundaries and pixel strides. + /// + private static void AssertEncodersMatchReferences() + { + int[] bytesPerPixels = [1, 2, 3, 4, 6, 8]; + int[] lengths = [8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 131, 132, 133, 135, 136, 137, 257]; + + foreach (int bytesPerPixel in bytesPerPixels) + { + foreach (int length in lengths) + { + if (length < bytesPerPixel) + { + continue; + } + + byte[] scanline = new byte[length]; + byte[] previousScanline = new byte[length]; + Random random = new((bytesPerPixel * 397) + length); + random.NextBytes(scanline); + random.NextBytes(previousScanline); + + AssertFilterMatchesReference( + PngFilterMethod.Sub, + scanline, + previousScanline, + bytesPerPixel); + + AssertFilterMatchesReference( + PngFilterMethod.Up, + scanline, + previousScanline, + bytesPerPixel); + + AssertFilterMatchesReference( + PngFilterMethod.Average, + scanline, + previousScanline, + bytesPerPixel); + + AssertFilterMatchesReference( + PngFilterMethod.Paeth, + scanline, + previousScanline, + bytesPerPixel); + } + } + } + + /// + /// Compares one filter result and variance sum with its scalar reference. + /// + /// The filter to evaluate. + /// The current scanline. + /// The preceding scanline. + /// The component distance between adjacent pixels. + private static void AssertFilterMatchesReference( + PngFilterMethod filter, + byte[] scanline, + byte[] previousScanline, + int bytesPerPixel) + { + byte[] expected = new byte[scanline.Length + 1]; + byte[] actual = new byte[scanline.Length + 1]; + int expectedSum; + int actualSum; + + switch (filter) + { + case PngFilterMethod.Sub: + ReferenceImplementations.EncodeSubFilter(scanline, expected, bytesPerPixel, out expectedSum); + SubFilter.Encode(scanline, actual, bytesPerPixel, out actualSum); + break; + + case PngFilterMethod.Up: + ReferenceImplementations.EncodeUpFilter(scanline, previousScanline, expected, out expectedSum); + UpFilter.Encode(scanline, previousScanline, actual, out actualSum); + break; + + case PngFilterMethod.Average: + ReferenceImplementations.EncodeAverageFilter( + scanline, + previousScanline, + expected, + bytesPerPixel, + out expectedSum); + + AverageFilter.Encode( + scanline, + previousScanline, + actual, + (uint)bytesPerPixel, + out actualSum); + + break; + + case PngFilterMethod.Paeth: + ReferenceImplementations.EncodePaethFilter( + scanline, + previousScanline, + expected, + bytesPerPixel, + out expectedSum); + + PaethFilter.Encode( + scanline, + previousScanline, + actual, + bytesPerPixel, + out actualSum); + + break; + + default: + throw new InvalidOperationException(); + } + + Assert.Equal(expectedSum, actualSum); + Assert.Equal(expected, actual); + } + public class TestData { private readonly PngFilterMethod filter; From 71d27f4b85122f403ff5a5f167282da9789973e5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 09:33:38 +1000 Subject: [PATCH 226/240] Correct SIMD helper accessibility --- .../Helpers/Shuffle/IComponentShuffle.cs | 4 ++-- .../Common/Helpers/Shuffle/IShuffle4.cs | 4 ++-- .../JpegColorConverter.Operator.cs | 22 +++++++++---------- .../Formats/Png/Filters/IPngFilterOperator.cs | 16 +++++++------- .../PixelBlenders/IPixelBlenderOperator.cs | 6 ++--- .../Utils/Vector4Converters.Affine.cs | 4 ++-- .../Vector4Converters.AffineOperators.cs | 8 +++---- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 6b80845ed..b1d545ed8 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -15,12 +15,12 @@ internal interface IComponentShuffle /// /// The source components, with the first component in the least-significant byte. /// The reordered packed components. - static abstract uint Invoke(uint source); + public static abstract uint Invoke(uint source); /// /// Reorders the packed pixels in a 128-bit vector. /// /// The source pixels. /// The reordered pixels. - static abstract Vector128 Invoke(Vector128 source); + public static abstract Vector128 Invoke(Vector128 source); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs index 56ea59df5..2a5a6668f 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs @@ -19,14 +19,14 @@ internal interface IShuffle4 : IComponentShuffle /// /// The source pixels. /// The reordered pixels. - static abstract Vector256 Invoke(Vector256 source); + public static abstract Vector256 Invoke(Vector256 source); /// /// Reorders the packed pixels in a 512-bit vector. /// /// The source pixels. /// The reordered pixels. - static abstract Vector512 Invoke(Vector512 source); + public static abstract Vector512 Invoke(Vector512 source); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs index be9cedcb0..86851a9d6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs @@ -23,12 +23,12 @@ internal abstract partial class JpegColorConverterBase /// /// Gets the JPEG color space handled by the operator. /// - static abstract JpegColorSpace ColorSpace { get; } + public static abstract JpegColorSpace ColorSpace { get; } /// /// Gets the number of component planes used by the color space. /// - static abstract int ComponentCount { get; } + public static abstract int ComponentCount { get; } /// /// Converts one JPEG sample to normalized RGB. @@ -40,7 +40,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of . - static abstract void ConvertToRgb( + public static abstract void ConvertToRgb( ref float c0, ref float c1, ref float c2, @@ -59,7 +59,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - static abstract void ConvertToRgb( + public static abstract void ConvertToRgb( ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, @@ -78,7 +78,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - static abstract void ConvertToRgb( + public static abstract void ConvertToRgb( ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, @@ -97,7 +97,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - static abstract void ConvertToRgb( + public static abstract void ConvertToRgb( ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, @@ -119,7 +119,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component. /// The third converted component. /// The fourth converted component, if used. - static abstract void ConvertFromRgb( + public static abstract void ConvertFromRgb( float r, float g, float b, @@ -144,7 +144,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - static abstract void ConvertFromRgb( + public static abstract void ConvertFromRgb( Vector128 r, Vector128 g, Vector128 b, @@ -169,7 +169,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - static abstract void ConvertFromRgb( + public static abstract void ConvertFromRgb( Vector256 r, Vector256 g, Vector256 b, @@ -194,7 +194,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - static abstract void ConvertFromRgb( + public static abstract void ConvertFromRgb( Vector512 r, Vector512 g, Vector512 b, @@ -213,7 +213,7 @@ internal abstract partial class JpegColorConverterBase /// The source ICC profile. /// The component values to convert. /// The maximum component value for the configured precision. - static abstract void ConvertToRgbInPlaceWithIcc( + public static abstract void ConvertToRgbInPlaceWithIcc( Configuration configuration, IccProfile profile, in ComponentValues values, diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs index 1113a5df6..9fcec7b78 100644 --- a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs +++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs @@ -16,22 +16,22 @@ internal interface IPngFilterOperator /// /// Gets the filter type written to the leading result byte. /// - static abstract FilterType Type { get; } + public static abstract FilterType Type { get; } /// /// Gets a value indicating whether the predictor reads the left component. /// - static abstract bool UsesLeft { get; } + public static abstract bool UsesLeft { get; } /// /// Gets a value indicating whether the predictor reads the above component. /// - static abstract bool UsesAbove { get; } + public static abstract bool UsesAbove { get; } /// /// Gets a value indicating whether the predictor reads the upper-left component. /// - static abstract bool UsesUpperLeft { get; } + public static abstract bool UsesUpperLeft { get; } /// /// Filters one byte from its PNG neighborhood. @@ -41,7 +41,7 @@ internal interface IPngFilterOperator /// The corresponding component in the preceding scanline. /// The preceding component in the preceding scanline. /// The filtered residual. - static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft); + public static abstract byte Invoke(byte scan, byte left, byte above, byte upperLeft); /// /// Filters sixteen byte lanes from their PNG neighborhoods. @@ -51,7 +51,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - static abstract Vector128 Invoke( + public static abstract Vector128 Invoke( Vector128 scan, Vector128 left, Vector128 above, @@ -65,7 +65,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - static abstract Vector256 Invoke( + public static abstract Vector256 Invoke( Vector256 scan, Vector256 left, Vector256 above, @@ -79,7 +79,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - static abstract Vector512 Invoke( + public static abstract Vector512 Invoke( Vector512 scan, Vector512 left, Vector512 above, diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs index bdb43e7be..902ba9e25 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs @@ -18,7 +18,7 @@ internal interface IPixelBlenderOperator /// The source RGBA lanes. /// The source opacity in the range 0 through 1. /// The blended RGBA lanes. - static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); + public static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); /// /// Blends two pixels represented by two consecutive groups of four RGBA lanes. @@ -27,7 +27,7 @@ internal interface IPixelBlenderOperator /// The source RGBA lanes. /// The source opacity repeated across each pixel's four lanes. /// The blended RGBA lanes. - static abstract Vector256 Invoke( + public static abstract Vector256 Invoke( Vector256 background, Vector256 source, Vector256 amount); @@ -39,7 +39,7 @@ internal interface IPixelBlenderOperator /// The source RGBA lanes. /// The source opacity repeated across each pixel's four lanes. /// The blended RGBA lanes. - static abstract Vector512 Invoke( + public static abstract Vector512 Invoke( Vector512 background, Vector512 source, Vector512 amount); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs index fba3b8fcd..0aef79148 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs @@ -16,7 +16,7 @@ internal static partial class Vector4Converters /// 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) + public static void MultiplyThenAdd(Span vectors, Vector4 multiplier, Vector4 offset) => Apply(vectors, new MultiplyThenAddOperator(multiplier, offset)); /// @@ -25,7 +25,7 @@ internal static partial class Vector4Converters /// 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) + public static void AddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) => Apply(vectors, new AddThenDivideOperator(offset, divisor)); /// diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs index f5e57f765..0b08061bc 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs @@ -19,28 +19,28 @@ internal static partial class Vector4Converters /// /// The source components. /// The transformed components. - Vector4 Invoke(Vector4 source); + public Vector4 Invoke(Vector4 source); /// /// Transforms one pixel represented by the four single-precision lanes in a 128-bit register. /// /// The source components. /// The transformed components. - Vector128 Invoke(Vector128 source); + public Vector128 Invoke(Vector128 source); /// /// Transforms two pixels represented by the eight single-precision lanes in a 256-bit register. /// /// The source components. /// The transformed components. - Vector256 Invoke(Vector256 source); + public Vector256 Invoke(Vector256 source); /// /// Transforms four pixels represented by the sixteen single-precision lanes in a 512-bit register. /// /// The source components. /// The transformed components. - Vector512 Invoke(Vector512 source); + public Vector512 Invoke(Vector512 source); } /// From ca126a5f4acfad77e6cda4ffb0543e40576ba8c9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 13:35:32 +1000 Subject: [PATCH 227/240] Remove replaced SIMD implementations --- .../JpegColorConverter.CmykOperator.cs | 30 +- .../JpegColorConverter.CmykScalar.cs | 116 -- .../JpegColorConverter.CmykVector128.cs | 100 -- .../JpegColorConverter.CmykVector256.cs | 100 -- .../JpegColorConverter.CmykVector512.cs | 108 -- .../JpegColorConverter.GrayScaleOperator.cs | 29 +- .../JpegColorConverter.GrayScaleScalar.cs | 97 -- .../JpegColorConverter.GrayScaleVector128.cs | 81 -- .../JpegColorConverter.GrayScaleVector256.cs | 81 -- .../JpegColorConverter.GrayScaleVector512.cs | 89 -- .../JpegColorConverter.RgbOperator.cs | 27 +- .../JpegColorConverter.RgbScalar.cs | 84 -- .../JpegColorConverter.RgbVector128.cs | 56 - .../JpegColorConverter.RgbVector256.cs | 56 - .../JpegColorConverter.RgbVector512.cs | 64 - .../JpegColorConverter.TiffCmykOperator.cs | 29 +- .../JpegColorConverter.TiffCmykScalar.cs | 118 -- .../JpegColorConverter.TiffCmykVector128.cs | 99 -- .../JpegColorConverter.TiffCmykVector256.cs | 99 -- .../JpegColorConverter.TiffCmykVector512.cs | 108 -- .../JpegColorConverter.TiffYccKOperator.cs | 56 +- .../JpegColorConverter.TiffYccKScalar.cs | 153 --- .../JpegColorConverter.TiffYccKVector128.cs | 131 -- .../JpegColorConverter.TiffYccKVector256.cs | 131 -- .../JpegColorConverter.TiffYccKVector512.cs | 142 -- .../JpegColorConverter.YCbCrOperator.cs | 81 +- .../JpegColorConverter.YCbCrScalar.cs | 121 -- .../JpegColorConverter.YCbCrVector128.cs | 121 -- .../JpegColorConverter.YCbCrVector256.cs | 121 -- .../JpegColorConverter.YCbCrVector512.cs | 128 -- .../JpegColorConverter.YccKOperator.cs | 56 +- .../JpegColorConverter.YccKScalar.cs | 125 -- .../JpegColorConverter.YccKVector128.cs | 135 -- .../JpegColorConverter.YccKVector256.cs | 135 -- .../JpegColorConverter.YccKVector512.cs | 143 -- .../JpegColorConverterScalar.cs | 23 - .../JpegColorConverterVector.cs | 129 -- .../JpegColorConverterVector128.cs | 34 - .../JpegColorConverterVector256.cs | 34 - .../JpegColorConverterVector512.cs | 111 -- .../ColorConversion/CmykColorConversion.cs | 37 +- .../GrayscaleColorConversion.cs | 37 +- .../JpegColorConverterOperatorComparison.cs | 239 ---- .../JpegColorConverterTraversalAssembly.cs | 325 ----- .../ColorConversion/RgbColorConversion.cs | 37 +- .../ColorConversion/YCbCrColorConversion.cs | 37 +- .../YCbCrOperatorComparison.cs | 127 -- .../ColorConversion/YccKColorConverter.cs | 37 +- .../Codecs/Png/PngFilterEncode.cs | 94 +- .../Codecs/Png/PngFilterEncodeAssembly.cs | 54 +- .../Codecs/Png/PngFilterEncodeBaseline.cs | 470 ------- .../General/BasicMath/AddSpan.cs | 65 - .../General/BasicMath/NormalizeSpan.cs | 61 - .../BasicMath/TensorPrimitivesAssembly.cs | 175 +++ .../TensorPrimitivesAssemblyComparison.cs | 813 ------------ .../PixelConversion/Vector4AffineTransform.cs | 160 +-- .../Vector4AffineTransformAssembly.cs | 14 - .../Formats/Jpg/JpegColorConverterTests.cs | 1149 +++++------------ 58 files changed, 827 insertions(+), 6785 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs create mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs index 48281c34a..dad093124 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -240,6 +245,29 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // JPEG CMYK stores inverted components, while the ICC converter consumes normalized conventional CMYK. + PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs deleted file mode 100644 index ebaa7c4b0..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykScalar : JpegColorConverterScalar - { - public CmykScalar(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) => - ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i]; - float m = c1[i]; - float y = c2[i]; - float k = c3[i]; - - k *= scale; - c0[i] = c * k; - c1[i] = m * k; - c2[i] = y * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255f - rLane[i]; - float mtmp = 255f - gLane[i]; - float ytmp = 255f - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255f) - { - ctmp = 0f; - mtmp = 0f; - ytmp = 0f; - } - else - { - ctmp = (ctmp - ktmp) / (255f - ktmp); - mtmp = (mtmp - ktmp) / (255f - ktmp); - ytmp = (ytmp - ktmp) / (255f - ktmp); - } - - c[i] = maxValue - (ctmp * maxValue); - m[i] = maxValue - (mtmp * maxValue); - y[i] = maxValue - (ytmp * maxValue); - k[i] = maxValue - ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs deleted file mode 100644 index 14addafc1..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector128 : JpegColorConverterVector128 - { - public CmykVector128(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs deleted file mode 100644 index 98bda53d2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector256 : JpegColorConverterVector256 - { - public CmykVector256(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs deleted file mode 100644 index c72af2faf..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector512 : JpegColorConverterVector512 - { - public CmykVector512(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => CmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => CmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs index 2ac827da7..20b68e8b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -198,6 +203,28 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + float scale = 1F / maximumValue; + + // ICC luminance values are normalized, so the source plane is scaled in place before conversion. + TensorPrimitives_.Multiply(c0, scale, c0); + + Span source = MemoryMarshal.Cast(c0); + Span destination = MemoryMarshal.Cast(packed); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs deleted file mode 100644 index 74869c93c..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleScalar : JpegColorConverterScalar - { - public GrayScaleScalar(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbScalar(values, rLane, gLane, bLane); - - internal static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); - ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); - ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); - - float scale = 1F / maxValue; - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - float c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - ref float c0Base = ref MemoryMarshal.GetReference(c0); - ref float c1Base = ref MemoryMarshal.GetReference(c1); - ref float c2Base = ref MemoryMarshal.GetReference(c2); - - float scale = 1F / maxValue; - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - ref float c = ref Unsafe.Add(ref c0Base, i); - c *= scale; - } - - Span source = MemoryMarshal.Cast(values.Component0); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertFromRgbScalar(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - Span c0 = values.Component0; - - for (int i = 0; i < c0.Length; i++) - { - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - c0[i] = (float)((0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i])); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs deleted file mode 100644 index 3c4a64f80..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector128 : JpegColorConverterVector128 - { - public GrayScaleVector128(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref srcRed, i); - ref Vector128 g = ref Unsafe.Add(ref srcGreen, i); - 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_.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 deleted file mode 100644 index 3d98d1eff..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector256 : JpegColorConverterVector256 - { - public GrayScaleVector256(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref srcRed, i); - ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); - 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_.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 deleted file mode 100644 index 96126ac9d..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector512 : JpegColorConverterVector512 - { - public GrayScaleVector512(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 c = Unsafe.Add(ref c0Base, i) * scale; - - Unsafe.Add(ref c0Base, i) = c; - Unsafe.Add(ref c1Base, i) = c; - Unsafe.Add(ref c2Base, i) = c; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref srcRed, i); - ref Vector512 g = ref Unsafe.Add(ref srcGreen, i); - 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_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => GrayScaleScalar.ConvertFromRgbScalar(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs index 6d7281dc6..8559b818d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -179,6 +184,26 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + // JPEG planes use the integer sample domain, while ICC RGB values are normalized and interleaved. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); + + Span rgb = MemoryMarshal.Cast(packed); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(rgb, rgb); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..rgb.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs deleted file mode 100644 index 92be9e896..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbScalar : JpegColorConverterScalar - { - public RgbScalar(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, rLane, gLane, bLane); - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertToRgbInPlace(ComponentValues values, float maxValue) - { - ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); - ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); - ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); - - float scale = 1F / maxValue; - - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - Unsafe.Add(ref c0Base, i) *= scale; - Unsafe.Add(ref c1Base, i) *= scale; - Unsafe.Add(ref c2Base, i) *= scale; - } - } - - internal static void ConvertFromRgb(ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // TODO: This doesn't seem correct. We should be scaling to the maximum value here. - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs deleted file mode 100644 index 6cbbc7c7c..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector128 : JpegColorConverterVector128 - { - public RgbVector128(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref rBase, i); - ref Vector128 g = ref Unsafe.Add(ref gBase, i); - ref Vector128 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs deleted file mode 100644 index 10bc2be5f..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector256 : JpegColorConverterVector256 - { - public RgbVector256(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref rBase, i); - ref Vector256 g = ref Unsafe.Add(ref gBase, i); - ref Vector256 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs deleted file mode 100644 index 6e01ad7cb..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector512 : JpegColorConverterVector512 - { - public RgbVector512(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref rBase, i); - ref Vector512 g = ref Unsafe.Add(ref gBase, i); - ref Vector512 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => RgbScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => RgbScalar.ConvertFromRgb(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs index 88b6c2411..f2c20959d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -154,6 +159,28 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // TIFF CMYK is already non-inverted, so only normalization and interleaving precede ICC conversion. + PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs deleted file mode 100644 index 27449a368..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffCmykScalar : JpegColorConverterScalar - { - public TiffCmykScalar(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / maxValue; - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i] * scale; - float m = c1[i] * scale; - float y = c2[i] * scale; - float k = 1 - (c3[i] * scale); - - c0[i] = (1 - c) * k; - c1[i] = (1 - m) * k; - c2[i] = (1 - y) * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255F - rLane[i]; - float mtmp = 255F - gLane[i]; - float ytmp = 255F - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255F) - { - ctmp = 0F; - mtmp = 0F; - ytmp = 0F; - } - else - { - float divisor = 1 / (255F - ktmp); - ctmp = (ctmp - ktmp) * divisor; - mtmp = (mtmp - ktmp) * divisor; - ytmp = (ytmp - ktmp) * divisor; - } - - c[i] = ctmp * maxValue; - m[i] = mtmp * maxValue; - y[i] = ytmp * maxValue; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs deleted file mode 100644 index 6d52d5c72..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector128 : JpegColorConverterVector128 - { - public TiffCmykVector128(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k = Vector128.One - (k * scale); - c = (Vector128.One - (c * scale)) * k; - m = (Vector128.One - (m * scale)) * k; - y = (Vector128.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = Vector128.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs deleted file mode 100644 index 61b312a06..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector256 : JpegColorConverterVector256 - { - public TiffCmykVector256(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k = Vector256.One - (k * scale); - c = (Vector256.One - (c * scale)) * k; - m = (Vector256.One - (m * scale)) * k; - y = (Vector256.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = Vector256.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs deleted file mode 100644 index 51d5cc76d..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector512 : JpegColorConverterVector512 - { - public TiffCmykVector512(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k = Vector512.One - (k * scale); - c = (Vector512.One - (c * scale)) * k; - m = (Vector512.One - (m * scale)) * k; - y = (Vector512.One - (y * scale)) * k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = Vector512.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs index c66d266a1..63e14fb88 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -34,9 +39,9 @@ internal abstract partial class JpegColorConverterBase // TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the // remaining light after K modulate all three channels. - c0 = (y + (YCbCrScalar.RCrMult * cr)) * k; - c1 = (y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr)) * k; - c2 = (y + (YCbCrScalar.BCbMult * cb)) * k; + c0 = (y + (YCbCrOperator.RCrMult * cr)) * k; + c1 = (y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr)) * k; + c2 = (y + (YCbCrOperator.BCbMult * cb)) * k; } /// @@ -49,9 +54,9 @@ internal abstract partial class JpegColorConverterBase Vector128 k = Vector128.One - (c3 * scale); // Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -64,9 +69,9 @@ internal abstract partial class JpegColorConverterBase Vector256 k = Vector256.One - (c3 * scale); // Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -79,9 +84,9 @@ internal abstract partial class JpegColorConverterBase Vector512 k = Vector512.One - (c3 * scale); // Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -182,6 +187,31 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // TIFF YccK is non-inverted, so normalize directly before converting its JPEG-specific model to CMYK. + PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + converter.Convert(MemoryMarshal.Cast(source), source); + + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs deleted file mode 100644 index 01bfc0875..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffYccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public TiffYccKScalar(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1F / maxValue; - halfValue *= scale; - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i] * scale; - float cb = (c1[i] * scale) - halfValue; - float cr = (c2[i] * scale) - halfValue; - float scaledK = 1 - (c3[i] * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (y + (RCrMult * cr)) * scaledK; - c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK; - c2[i] = (y + (BCbMult * cb)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < cr.Length; i++) - { - // Scale down to [0-1] - const float divisor = 1F / 255F; - float r = rLane[i] * divisor; - float g = gLane[i] * divisor; - float b = bLane[i] * divisor; - - float ytmp; - float cbtmp; - float crtmp; - float ktmp = 1F - MathF.Max(r, MathF.Max(g, b)); - - if (ktmp >= 1F) - { - ytmp = 0F; - cbtmp = 0.5F; - crtmp = 0.5F; - ktmp = maxValue; - } - else - { - float kmask = 1F / (1F - ktmp); - r *= kmask; - g *= kmask; - b *= kmask; - - // Scale to [0-maxValue] - ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue; - cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue); - crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue); - ktmp *= maxValue; - } - - y[i] = ytmp; - cb[i] = cbtmp; - cr[i] = crtmp; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - - // YccK is not a defined ICC color space � it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs. - // ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform. - // This transform assumes Rec.601 YCbCr coefficients and an inverted K channel. - // - // The YccK => Cmyk conversion is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs deleted file mode 100644 index ae3391d42..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector128 : JpegColorConverterVector128 - { - public TiffYccKVector128(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1F / this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue) * scale; - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector128 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector128 y = c0 * scale; - Vector128 cb = (c1 * scale) - chromaOffset; - Vector128 cr = (c2 * scale) - chromaOffset; - Vector128 scaledK = Vector128.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 maxSourceValue = Vector128.Create(1 / 255F); - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i) * maxSourceValue; - Vector128 g = Unsafe.Add(ref srcG, i) * maxSourceValue; - Vector128 b = Unsafe.Add(ref srcB, i) * maxSourceValue; - Vector128 ktmp = Vector128.One - Vector128.Max(r, Vector128.Min(g, b)); - - Vector128 kMask = ~Vector128.Equals(ktmp, Vector128.One); - Vector128 divisor = Vector128.One / (Vector128.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // 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_.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); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs deleted file mode 100644 index ec6b228ca..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector256 : JpegColorConverterVector256 - { - public TiffYccKVector256(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1F / this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue) * scale; - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector256 y = c0 * scale; - Vector256 cb = (c1 * scale) - chromaOffset; - Vector256 cr = (c2 * scale) - chromaOffset; - Vector256 scaledK = Vector256.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 maxSourceValue = Vector256.Create(255F); - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector256 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector256 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector256 ktmp = Vector256.One - Vector256.Max(r, Vector256.Min(g, b)); - - Vector256 kMask = ~Vector256.Equals(ktmp, Vector256.One); - Vector256 divisor = Vector256.One / (Vector256.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // 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_.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); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs deleted file mode 100644 index 6a0fb93e2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector512 : JpegColorConverterVector512 - { - public TiffYccKVector512(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 scale = Vector512.Create(1F / this.MaximumValue); - Vector512 chromaOffset = Vector512.Create(this.HalfValue) * scale; - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector512 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector512 y = c0 * scale; - Vector512 cb = (c1 * scale) - chromaOffset; - Vector512 cr = (c2 * scale) - chromaOffset; - Vector512 scaledK = Vector512.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 maxSourceValue = Vector512.Create(255F); - Vector512 maxSampleValue = Vector512.Create(maxValue); - Vector512 chromaOffset = Vector512.Create(halfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector512 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector512 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector512 ktmp = Vector512.One - Vector512.Max(r, Vector512.Min(g, b)); - - Vector512 kMask = ~Vector512.Equals(ktmp, Vector512.One); - Vector512 divisor = Vector512.One / (Vector512.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // 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_.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); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs index ca8671e9f..931b96dff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -15,6 +20,26 @@ internal abstract partial class JpegColorConverterBase /// internal readonly struct YCbCrOperator : IJpegColorConverterOperator { + /// + /// The BT.601 red contribution from centered Cr. + /// + public const float RCrMult = 1.402F; + + /// + /// The BT.601 green contribution from centered Cb. + /// + public const float GCbMult = (float)(0.114 * 1.772 / 0.587); + + /// + /// The BT.601 green contribution from centered Cr. + /// + public const float GCrMult = (float)(0.299 * 1.402 / 0.587); + + /// + /// The BT.601 blue contribution from centered Cb. + /// + public const float BCbMult = 1.772F; + /// public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr; @@ -40,9 +65,9 @@ internal abstract partial class JpegColorConverterBase // the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized // to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the // destination pixel format owns saturation; retaining overshoot avoids discarding color information. - c0 = MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1 = MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2 = MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + c0 = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1 = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2 = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; } /// @@ -63,12 +88,12 @@ internal abstract partial class JpegColorConverterBase // Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available: // R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample // domain before the common normalization scale so all precisions use integer JPEG sample semantics. - Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y); Vector128 g = Vector128_.MultiplyAddEstimate( cr, - Vector128.Create(-YCbCrScalar.GCrMult), - Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + Vector128.Create(-GCrMult), + Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y); c0 = Vector128_.RoundToNearestInteger(r) * scale; c1 = Vector128_.RoundToNearestInteger(g) * scale; @@ -93,12 +118,12 @@ internal abstract partial class JpegColorConverterBase // These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload. // Keeping an explicit overload allows the JIT to emit native YMM operations without a width // switch or decomposing the vector into smaller values. - Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y); Vector256 g = Vector256_.MultiplyAddEstimate( cr, - Vector256.Create(-YCbCrScalar.GCrMult), - Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + Vector256.Create(-GCrMult), + Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y); c0 = Vector256_.RoundToNearestInteger(r) * scale; c1 = Vector256_.RoundToNearestInteger(g) * scale; @@ -123,12 +148,12 @@ internal abstract partial class JpegColorConverterBase // Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts; // assembly inspection verifies the JIT hoists them from the loop and retains fused operations. // The formula and rounding order remain identical to the narrower overloads. - Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y); Vector512 g = Vector512_.MultiplyAddEstimate( cr, - Vector512.Create(-YCbCrScalar.GCrMult), - Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + Vector512.Create(-GCrMult), + Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y); c0 = Vector512_.RoundToNearestInteger(r) * scale; c1 = Vector512_.RoundToNearestInteger(g) * scale; @@ -258,6 +283,30 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + // ICC profiles rarely expose YCbCr transforms, so BT.601 first produces RGB in the profile's source space. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed); + converter.Convert(source, destination); + + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(destination, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs deleted file mode 100644 index 2b1c1dca3..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrScalar : JpegColorConverterScalar - { - // derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YCbCrScalar(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - float scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - // Although YCbCr is a defined ICC color space, in practice ICC profiles - // do not implement transforms from it. - // Therefore, we first convert JPEG YCbCr to RGB manually, then perform - // color-managed conversion to the target profile. - // - // The YCbCr => RGB conversion is based on BT.601 and is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - ColorProfileConverter converter = new(); - - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - converter.Convert(source, destination); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(destination, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = rLane[i]; - float g = gLane[i]; - float b = bLane[i]; - - // 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) - y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs deleted file mode 100644 index 37847a6e6..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector128 : JpegColorConverterVector128 - { - public YCbCrVector128(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - b = Vector128_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i); - Vector128 g = Unsafe.Add(ref srcG, i); - Vector128 b = Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs deleted file mode 100644 index fbccf88e2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector256 : JpegColorConverterVector256 - { - public YCbCrVector256(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - b = Vector256_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i); - Vector256 g = Unsafe.Add(ref srcG, i); - Vector256 b = Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs deleted file mode 100644 index 387917175..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector512 : JpegColorConverterVector512 - { - public YCbCrVector512(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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; - b = Vector512_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i); - Vector512 g = Unsafe.Add(ref srcG, i); - Vector512 b = Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YCbCrScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs index ef7185dd3..fb85194e6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -32,9 +37,9 @@ internal abstract partial class JpegColorConverterBase // YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before // subtracting from max and applying K because changing that order changes encoded JPEG semantics. - c0 = (maximumValue - MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1 = (maximumValue - MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2 = (maximumValue - MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; + c0 = (maximumValue - MathF.Round(y + (YCbCrOperator.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1 = (maximumValue - MathF.Round(y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2 = (maximumValue - MathF.Round(y + (YCbCrOperator.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; } /// @@ -47,9 +52,9 @@ internal abstract partial class JpegColorConverterBase Vector128 scaledK = c3 * scale * scale; // Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane. - Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); - Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK; @@ -65,9 +70,9 @@ internal abstract partial class JpegColorConverterBase Vector256 scaledK = c3 * scale * scale; // Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB. - Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); - Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK; @@ -83,9 +88,9 @@ internal abstract partial class JpegColorConverterBase Vector512 scaledK = c3 * scale * scale; // Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code. - Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); - Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK; @@ -130,6 +135,31 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // Adobe-style JPEG YccK is inverted; normalize it before applying the format-defined YccK-to-CMYK transform. + PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + converter.Convert(MemoryMarshal.Cast(source), source); + + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs deleted file mode 100644 index 3368e52bf..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YccKScalar(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - float scaledK = c3[i] * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); - - // cmyk -> ycck - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = maxValue - c[i]; - float g = maxValue - m[i]; - float b = maxValue - y[i]; - - // k value is passed untouched from rgb -> cmyk conversion - c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - - // YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs. - // ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform. - // This transform assumes Rec.601 YCbCr coefficients and an inverted K channel. - // - // The YccK => Cmyk conversion is independent of any embedded ICC profile. - // Since the same RGB working space is used during conversion to and from XYZ, - // colorimetric accuracy is preserved. - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs deleted file mode 100644 index 279c100b2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector128 : JpegColorConverterVector128 - { - public YccKVector128(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector128 max = Vector128.Create(this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - Vector128 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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); - b = max - Vector128_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector128.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = ref destY; - ref Vector128 srcG = ref destCb; - ref Vector128 srcB = ref destCr; - - // Used for the color conversion - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector128 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector128 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs deleted file mode 100644 index 1bebdfcf4..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector256 : JpegColorConverterVector256 - { - public YccKVector256(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector256 max = Vector256.Create(this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - Vector256 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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); - b = max - Vector256_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector256.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = ref destY; - ref Vector256 srcG = ref destCb; - ref Vector256 srcB = ref destCr; - - // Used for the color conversion - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector256 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector256 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs deleted file mode 100644 index 9c0e1ab74..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector512 : JpegColorConverterVector512 - { - public YccKVector512(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector512 max = Vector512.Create(this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - Vector512 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - 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); - b = max - Vector512_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector512.ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = ref destY; - ref Vector512 srcG = ref destCb; - ref Vector512 srcB = ref destCr; - - // Used for the color conversion - Vector512 maxSampleValue = Vector512.Create(this.MaximumValue); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector512 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector512 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // 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_.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; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs deleted file mode 100644 index 13e5c6d5b..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on scalar instructions. - /// - internal abstract class JpegColorConverterScalar : JpegColorConverterBase - { - protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public sealed override bool IsAvailable => true; - - public sealed override int ElementsPerBatch => 1; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs deleted file mode 100644 index f3c3eb8db..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on API. - /// - /// - /// Converters of this family can work with data of any size. - /// Even though real life data is guaranteed to be of size - /// divisible by 8 newer SIMD instructions like AVX512 won't work with - /// such data out of the box. These converters have fallback code - /// for 'remainder' data. - /// - internal abstract class JpegColorConverterVector : JpegColorConverterBase - { - protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - /// - /// Gets a value indicating whether this converter is supported on current hardware. - /// - public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; - - /// - public sealed override bool IsAvailable => IsSupported; - - public override int ElementsPerBatch => Vector.Count; - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs deleted file mode 100644 index 5cbb376c7..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector128 : JpegColorConverterBase - { - protected JpegColorConverterVector128(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector128.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector128.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs deleted file mode 100644 index 61c37d846..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector256 : JpegColorConverterBase - { - protected JpegColorConverterVector256(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector256.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector256.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs deleted file mode 100644 index 0c7d032d4..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - internal abstract class JpegColorConverterVector512 : JpegColorConverterBase - { - protected JpegColorConverterVector512(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector512.IsHardwareAccelerated; - - /// - public override bool IsAvailable => IsSupported; - - /// - public override int ElementsPerBatch => Vector512.Count; - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index a1ba3fb8d..9f9dfe433 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class CmykColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); + + /// + /// Initializes a new instance of the class. + /// public CmykColorConversion() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one CMYK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.CmykVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 3ade4279f..5776f4720 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class GrayScaleColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); + + /// + /// Initializes a new instance of the class. + /// public GrayScaleColorConversion() : base(1) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one grayscale component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.GrayScaleVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs deleted file mode 100644 index b614583ca..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Compares each shared operator converter with the Vector512 converter it replaces. -/// -[Config(typeof(Config.Standard))] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class JpegColorConverterOperatorComparison -{ - private JpegColorConverterBase legacy; - private JpegColorConverterBase operatorConverter; - private float[] legacyC0; - private float[] legacyC1; - private float[] legacyC2; - private float[] legacyC3; - private float[] operatorC0; - private float[] operatorC1; - private float[] operatorC2; - private float[] operatorC3; - private float[] r; - private float[] g; - private float[] b; - private int componentCount; - - /// - /// Gets or sets the color model measured by the current benchmark case. - /// - [Params( - JpegColorModel.Grayscale, - JpegColorModel.Rgb, - JpegColorModel.Cmyk, - JpegColorModel.YCbCr, - JpegColorModel.YccK, - JpegColorModel.TiffCmyk, - JpegColorModel.TiffYccK)] - public JpegColorModel ColorModel { get; set; } - - /// - /// Gets or sets the number of pixels converted by each invocation. - /// - [Params(128, 1024)] - public int Count { get; set; } - - /// - /// Creates equivalent legacy and operator converters and their independent component buffers. - /// - [GlobalSetup] - public void Setup() - { - (JpegColorConverterBase Legacy, JpegColorConverterBase Operator, int ComponentCount) converters = - this.ColorModel switch - { - JpegColorModel.Grayscale => ( - new JpegColorConverterBase.GrayScaleVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 1), - JpegColorModel.Rgb => ( - new JpegColorConverterBase.RgbVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 3), - JpegColorModel.Cmyk => ( - new JpegColorConverterBase.CmykVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.YCbCr => ( - new JpegColorConverterBase.YCbCrVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 3), - JpegColorModel.YccK => ( - new JpegColorConverterBase.YccKVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.TiffCmyk => ( - new JpegColorConverterBase.TiffCmykVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.TiffYccK => ( - new JpegColorConverterBase.TiffYccKVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - _ => throw new InvalidOperationException(), - }; - - (this.legacy, this.operatorConverter, this.componentCount) = converters; - - Random random = new(42); - this.legacyC0 = CreateRandomValues(this.Count, random); - this.legacyC1 = CreateRandomValues(this.Count, random); - this.legacyC2 = CreateRandomValues(this.Count, random); - this.legacyC3 = CreateRandomValues(this.Count, random); - this.operatorC0 = this.legacyC0.ToArray(); - this.operatorC1 = this.legacyC1.ToArray(); - this.operatorC2 = this.legacyC2.ToArray(); - this.operatorC3 = this.legacyC3.ToArray(); - this.r = CreateRandomValues(this.Count, random); - this.g = CreateRandomValues(this.Count, random); - this.b = CreateRandomValues(this.Count, random); - } - - /// - /// Converts JPEG components to RGB using the replaced Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("ToRgb")] - public void LegacyToRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); - - this.legacy.ConvertToRgbInPlace(values); - } - - /// - /// Converts JPEG components to RGB using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("ToRgb")] - public void OperatorToRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); - - this.operatorConverter.ConvertToRgbInPlace(values); - } - - /// - /// Converts RGB to JPEG components using the replaced Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("FromRgb")] - public void LegacyFromRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); - - this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Converts RGB to JPEG components using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("FromRgb")] - public void OperatorFromRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); - - this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Creates a component view over the buffers owned by the legacy converter. - /// - /// The component view for the configured color model. - private JpegColorConverterBase.ComponentValues CreateLegacyValues() - => new( - this.componentCount, - this.legacyC0, - this.componentCount > 1 ? this.legacyC1 : this.legacyC0, - this.componentCount > 2 ? this.legacyC2 : this.legacyC0, - this.componentCount > 3 ? this.legacyC3 : []); - - /// - /// Creates a component view over the buffers owned by the operator converter. - /// - /// The component view for the configured color model. - private JpegColorConverterBase.ComponentValues CreateOperatorValues() - => new( - this.componentCount, - this.operatorC0, - this.componentCount > 1 ? this.operatorC1 : this.operatorC0, - this.componentCount > 2 ? this.operatorC2 : this.operatorC0, - this.componentCount > 3 ? this.operatorC3 : []); - - /// - /// Creates deterministic sample-domain values for one component plane. - /// - /// The number of samples to create. - /// The deterministic random source shared by setup. - /// The populated component plane. - private static float[] CreateRandomValues(int length, Random random) - { - float[] values = new float[length]; - - for (int i = 0; i < values.Length; i++) - { - values[i] = (float)random.NextDouble() * 255F; - } - - return values; - } - - /// - /// Identifies the JPEG color model used by a benchmark case. - /// - public enum JpegColorModel - { - /// - /// One luminance component. - /// - Grayscale, - - /// - /// Three direct RGB components. - /// - Rgb, - - /// - /// Four inverted Adobe CMYK components. - /// - Cmyk, - - /// - /// Three JPEG YCbCr components. - /// - YCbCr, - - /// - /// Four inverted Adobe YCCK components. - /// - YccK, - - /// - /// Four non-inverted TIFF CMYK components. - /// - TiffCmyk, - - /// - /// Four non-inverted TIFF YCCK components. - /// - TiffYccK, - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs deleted file mode 100644 index 4ec7db8a1..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Exposes every closed JPEG operator traversal beside the Vector512 implementation it replaces. -/// -/// -/// A 63-pixel buffer leaves 256-bit, 128-bit, and scalar remainders after the 512-bit loop, making -/// every operator overload visible in the generated traversal assembly on AVX-512 hardware. -/// -[Config(typeof(Config.Analysis))] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class JpegColorConverterTraversalAssembly -{ - private const int Count = 63; - - private readonly JpegColorConverterBase.GrayScaleVector512 grayscaleLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter grayscaleOperator = new(8); - private readonly JpegColorConverterBase.RgbVector512 rgbLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter rgbOperator = new(8); - private readonly JpegColorConverterBase.CmykVector512 cmykLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter cmykOperator = new(8); - private readonly JpegColorConverterBase.YCbCrVector512 yCbCrLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter yCbCrOperator = new(8); - private readonly JpegColorConverterBase.YccKVector512 yccKLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter yccKOperator = new(8); - private readonly JpegColorConverterBase.TiffCmykVector512 tiffCmykLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter tiffCmykOperator = new(8); - private readonly JpegColorConverterBase.TiffYccKVector512 tiffYccKLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter tiffYccKOperator = new(8); - - private readonly float[] legacyC0 = new float[Count]; - private readonly float[] legacyC1 = new float[Count]; - private readonly float[] legacyC2 = new float[Count]; - private readonly float[] legacyC3 = new float[Count]; - private readonly float[] operatorC0 = new float[Count]; - private readonly float[] operatorC1 = new float[Count]; - private readonly float[] operatorC2 = new float[Count]; - private readonly float[] operatorC3 = new float[Count]; - private readonly float[] r = new float[Count]; - private readonly float[] g = new float[Count]; - private readonly float[] b = new float[Count]; - - /// - /// Populates the component and RGB planes with deterministic sample-domain values. - /// - [GlobalSetup] - public void Setup() - { - Random random = new(42); - - for (int i = 0; i < Count; i++) - { - // Independent non-constant lanes prevent the JIT from folding arithmetic or mask decisions. - this.legacyC0[i] = this.operatorC0[i] = (float)random.NextDouble() * 255F; - this.legacyC1[i] = this.operatorC1[i] = (float)random.NextDouble() * 255F; - this.legacyC2[i] = this.operatorC2[i] = (float)random.NextDouble() * 255F; - this.legacyC3[i] = this.operatorC3[i] = (float)random.NextDouble() * 255F; - this.r[i] = (float)random.NextDouble() * 255F; - this.g[i] = (float)random.NextDouble() * 255F; - this.b[i] = (float)random.NextDouble() * 255F; - } - } - - /// - /// Runs the replaced grayscale component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Grayscale.ToRgb")] - public void GrayscaleLegacyToRgb() - => this.grayscaleLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(1)); - - /// - /// Runs the shared grayscale component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Grayscale.ToRgb")] - public void GrayscaleOperatorToRgb() - => this.grayscaleOperator.ConvertToRgbInPlace(this.CreateOperatorValues(1)); - - /// - /// Runs the replaced grayscale RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Grayscale.FromRgb")] - public void GrayscaleLegacyFromRgb() - => this.grayscaleLegacy.ConvertFromRgb(this.CreateLegacyValues(1), this.r, this.g, this.b); - - /// - /// Runs the shared grayscale RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Grayscale.FromRgb")] - public void GrayscaleOperatorFromRgb() - => this.grayscaleOperator.ConvertFromRgb(this.CreateOperatorValues(1), this.r, this.g, this.b); - - /// - /// Runs the replaced RGB component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Rgb.ToRgb")] - public void RgbLegacyToRgb() - => this.rgbLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); - - /// - /// Runs the shared RGB component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Rgb.ToRgb")] - public void RgbOperatorToRgb() - => this.rgbOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); - - /// - /// Runs the replaced RGB RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Rgb.FromRgb")] - public void RgbLegacyFromRgb() - => this.rgbLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); - - /// - /// Runs the shared RGB RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Rgb.FromRgb")] - public void RgbOperatorFromRgb() - => this.rgbOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); - - /// - /// Runs the replaced CMYK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Cmyk.ToRgb")] - public void CmykLegacyToRgb() - => this.cmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared CMYK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Cmyk.ToRgb")] - public void CmykOperatorToRgb() - => this.cmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced CMYK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Cmyk.FromRgb")] - public void CmykLegacyFromRgb() - => this.cmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared CMYK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Cmyk.FromRgb")] - public void CmykOperatorFromRgb() - => this.cmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced YCbCr component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YCbCr.ToRgb")] - public void YCbCrLegacyToRgb() - => this.yCbCrLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); - - /// - /// Runs the shared YCbCr component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("YCbCr.ToRgb")] - public void YCbCrOperatorToRgb() - => this.yCbCrOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); - - /// - /// Runs the replaced YCbCr RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YCbCr.FromRgb")] - public void YCbCrLegacyFromRgb() - => this.yCbCrLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); - - /// - /// Runs the shared YCbCr RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("YCbCr.FromRgb")] - public void YCbCrOperatorFromRgb() - => this.yCbCrOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); - - /// - /// Runs the replaced YCCK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YccK.ToRgb")] - public void YccKLegacyToRgb() - => this.yccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared YCCK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("YccK.ToRgb")] - public void YccKOperatorToRgb() - => this.yccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced YCCK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YccK.FromRgb")] - public void YccKLegacyFromRgb() - => this.yccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared YCCK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("YccK.FromRgb")] - public void YccKOperatorFromRgb() - => this.yccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced TIFF CMYK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffCmyk.ToRgb")] - public void TiffCmykLegacyToRgb() - => this.tiffCmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared TIFF CMYK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffCmyk.ToRgb")] - public void TiffCmykOperatorToRgb() - => this.tiffCmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced TIFF CMYK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffCmyk.FromRgb")] - public void TiffCmykLegacyFromRgb() - => this.tiffCmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared TIFF CMYK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffCmyk.FromRgb")] - public void TiffCmykOperatorFromRgb() - => this.tiffCmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced TIFF YCCK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffYccK.ToRgb")] - public void TiffYccKLegacyToRgb() - => this.tiffYccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared TIFF YCCK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffYccK.ToRgb")] - public void TiffYccKOperatorToRgb() - => this.tiffYccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced TIFF YCCK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffYccK.FromRgb")] - public void TiffYccKLegacyFromRgb() - => this.tiffYccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared TIFF YCCK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffYccK.FromRgb")] - public void TiffYccKOperatorFromRgb() - => this.tiffYccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Creates a correctly aliased component view over the legacy planes. - /// - /// The number of component planes owned by the color model. - /// The legacy component view. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private JpegColorConverterBase.ComponentValues CreateLegacyValues(int componentCount) - => new( - componentCount, - this.legacyC0, - componentCount > 1 ? this.legacyC1 : this.legacyC0, - componentCount > 2 ? this.legacyC2 : this.legacyC0, - componentCount > 3 ? this.legacyC3 : []); - - /// - /// Creates a correctly aliased component view over the operator planes. - /// - /// The number of component planes owned by the color model. - /// The operator component view. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private JpegColorConverterBase.ComponentValues CreateOperatorValues(int componentCount) - => new( - componentCount, - this.operatorC0, - componentCount > 1 ? this.operatorC1 : this.operatorC0, - componentCount > 2 ? this.operatorC2 : this.operatorC0, - componentCount > 3 ? this.operatorC3 : []); -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 2916dcdce..d72bbb40b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class RgbColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); + + /// + /// Initializes a new instance of the class. + /// public RgbColorConversion() : base(3) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one RGB component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.RgbVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index fbd762af4..70936153a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YCbCrColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); + + /// + /// Initializes a new instance of the class. + /// public YCbCrColorConversion() : base(3) { } + /// + /// Converts one YCbCr component row through the adaptive operator traversal. + /// [Benchmark] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs deleted file mode 100644 index bba07b1e1..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Compares the shared YCbCr operator traversal with the Vector512 implementation it replaces. -/// -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class YCbCrOperatorComparison -{ - private JpegColorConverterBase.YCbCrVector512 legacy; - private JpegColorConverterBase.JpegColorConverter operatorConverter; - private float[] legacyC0; - private float[] legacyC1; - private float[] legacyC2; - private float[] operatorC0; - private float[] operatorC1; - private float[] operatorC2; - private float[] r; - private float[] g; - private float[] b; - - /// - /// Gets or sets the number of pixels converted by each invocation. - /// - [Params(8, 128, 1024)] - public int Count { get; set; } - - /// - /// Creates equivalent converter inputs in independent component buffers. - /// - [GlobalSetup] - public void Setup() - { - this.legacy = new JpegColorConverterBase.YCbCrVector512(8); - this.operatorConverter = - new JpegColorConverterBase.JpegColorConverter(8); - - Random random = new(42); - this.legacyC0 = CreateRandomValues(this.Count, random); - this.legacyC1 = CreateRandomValues(this.Count, random); - this.legacyC2 = CreateRandomValues(this.Count, random); - this.operatorC0 = this.legacyC0.ToArray(); - this.operatorC1 = this.legacyC1.ToArray(); - this.operatorC2 = this.legacyC2.ToArray(); - this.r = CreateRandomValues(this.Count, random); - this.g = CreateRandomValues(this.Count, random); - this.b = CreateRandomValues(this.Count, random); - } - - /// - /// Converts YCbCr components to RGB using the Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("ToRgb")] - public void LegacyToRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); - - this.legacy.ConvertToRgbInPlace(values); - } - - /// - /// Converts YCbCr components to RGB using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("ToRgb")] - public void OperatorToRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); - - this.operatorConverter.ConvertToRgbInPlace(values); - } - - /// - /// Converts RGB to YCbCr components using the Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("FromRgb")] - public void LegacyFromRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); - - this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Converts RGB to YCbCr components using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("FromRgb")] - public void OperatorFromRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); - - this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Creates deterministic sample-domain values for one component plane. - /// - /// The number of samples to create. - /// The deterministic random source shared by setup. - /// The populated component plane. - private static float[] CreateRandomValues(int length, Random random) - { - float[] values = new float[length]; - - for (int i = 0; i < values.Length; i++) - { - values[i] = (float)random.NextDouble() * 255F; - } - - return values; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index e6b04c152..80adff5cc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YccKColorConverter : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); + + /// + /// Initializes a new instance of the class. + /// public YccKColorConverter() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one YccK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YccKVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs index 0547376b7..e5a434067 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; /// -/// Compares the shared PNG map/reduce traversal with the filter-specific traversals it replaces. +/// Measures the shared PNG filter map/reduce traversal. /// [Config(typeof(Config.Short))] public class PngFilterEncode @@ -17,8 +17,7 @@ public class PngFilterEncode private byte[] scanline; private byte[] previousScanline; - private byte[] currentResult; - private byte[] baselineResult; + private byte[] result; /// /// Gets or sets the filter evaluated by each invocation. @@ -33,15 +32,14 @@ public class PngFilterEncode public int Count { get; set; } /// - /// Creates deterministic non-uniform inputs and independent result buffers. + /// Creates deterministic non-uniform inputs and a result buffer. /// [GlobalSetup] public void Setup() { this.scanline = new byte[this.Count]; this.previousScanline = new byte[this.Count]; - this.currentResult = new byte[this.Count + 1]; - this.baselineResult = new byte[this.Count + 1]; + this.result = new byte[this.Count + 1]; Random random = new(12345678); random.NextBytes(this.scanline); @@ -49,109 +47,53 @@ public class PngFilterEncode } /// - /// Executes the operator-driven map/reduce traversal. + /// Executes the shared operator-driven map/reduce traversal. /// /// The filter variance sum. [Benchmark] - public int Current() + public int Encode() => this.Filter switch { - PngFilterMethod.Sub => this.EncodeSubCurrent(), - PngFilterMethod.Up => this.EncodeUpCurrent(), - PngFilterMethod.Average => this.EncodeAverageCurrent(), - PngFilterMethod.Paeth => this.EncodePaethCurrent(), + PngFilterMethod.Sub => this.EncodeSub(), + PngFilterMethod.Up => this.EncodeUp(), + PngFilterMethod.Average => this.EncodeAverage(), + PngFilterMethod.Paeth => this.EncodePaeth(), _ => throw new InvalidOperationException() }; - /// - /// Executes the filter-specific traversal being replaced. - /// - /// The filter variance sum. - [Benchmark(Baseline = true)] - public int Baseline() - { - int sum; - - switch (this.Filter) - { - case PngFilterMethod.Sub: - PngFilterEncodeBaseline.EncodeSub( - this.scanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - case PngFilterMethod.Up: - PngFilterEncodeBaseline.EncodeUp( - this.scanline, - this.previousScanline, - this.baselineResult, - out sum); - - break; - - case PngFilterMethod.Average: - PngFilterEncodeBaseline.EncodeAverage( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - case PngFilterMethod.Paeth: - PngFilterEncodeBaseline.EncodePaeth( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - default: - throw new InvalidOperationException(); - } - - return sum; - } - /// /// Executes the current Sub encoder. /// - private int EncodeSubCurrent() + private int EncodeSub() { - SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out int sum); + SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out int sum); return sum; } /// /// Executes the current Up encoder. /// - private int EncodeUpCurrent() + private int EncodeUp() { - UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out int sum); + UpFilter.Encode(this.scanline, this.previousScanline, this.result, out int sum); return sum; } /// /// Executes the current Average encoder. /// - private int EncodeAverageCurrent() + private int EncodeAverage() { - AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); return sum; } /// /// Executes the current Paeth encoder. /// - private int EncodePaethCurrent() + private int EncodePaeth() { - PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); return sum; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs index bf8a18c12..5461cb149 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; /// -/// Exposes every normalized PNG filter and retained baseline for assembly comparison. +/// Exposes every normalized PNG filter for assembly inspection. /// [Config(typeof(Config.Analysis))] public class PngFilterEncodeAssembly @@ -17,8 +17,7 @@ public class PngFilterEncodeAssembly private byte[] scanline; private byte[] previousScanline; - private byte[] currentResult; - private byte[] baselineResult; + private byte[] result; /// /// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths. @@ -28,8 +27,7 @@ public class PngFilterEncodeAssembly { this.scanline = new byte[Count]; this.previousScanline = new byte[Count]; - this.currentResult = new byte[Count + 1]; - this.baselineResult = new byte[Count + 1]; + this.result = new byte[Count + 1]; Random random = new(12345678); random.NextBytes(this.scanline); @@ -41,64 +39,26 @@ public class PngFilterEncodeAssembly /// [Benchmark] public void Sub() - => SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out _); + => SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out _); /// /// Executes the normalized Up encoder. /// [Benchmark] public void Up() - => UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out _); + => UpFilter.Encode(this.scanline, this.previousScanline, this.result, out _); /// /// Executes the normalized Average encoder. /// [Benchmark] public void Average() - => AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); + => AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); /// /// Executes the normalized Paeth encoder. /// [Benchmark] public void Paeth() - => PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); - - /// - /// Executes the retained Sub encoder. - /// - [Benchmark] - public void BaselineSub() - => PngFilterEncodeBaseline.EncodeSub(this.scanline, this.baselineResult, BytesPerPixel, out _); - - /// - /// Executes the retained Up encoder. - /// - [Benchmark] - public void BaselineUp() - => PngFilterEncodeBaseline.EncodeUp(this.scanline, this.previousScanline, this.baselineResult, out _); - - /// - /// Executes the retained Average encoder. - /// - [Benchmark] - public void BaselineAverage() - => PngFilterEncodeBaseline.EncodeAverage( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out _); - - /// - /// Executes the retained Paeth encoder. - /// - [Benchmark] - public void BaselinePaeth() - => PngFilterEncodeBaseline.EncodePaeth( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out _); + => PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs deleted file mode 100644 index e28cc8105..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs +++ /dev/null @@ -1,470 +0,0 @@ -// 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.Benchmarks.Codecs.Png; - -/// -/// Retains the filter-specific PNG encode traversals for direct performance comparison. -/// -internal static class PngFilterEncodeBaseline -{ - /// - /// Executes the filter-specific Sub traversal. - /// - public static void EncodeSub( - ReadOnlySpan scanline, - Span result, - int bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 1; - - nuint x = 0; - - for (; x < (uint)bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = scan; - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 residual = Avx2.Subtract(scan, left); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector residual = scan - left; - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - left); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Up traversal. - /// - public static void EncodeUp( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 2; - - nuint x = 0; - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (; (int)x <= scanline.Length - Vector256.Count;) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 residual = Avx2.Subtract(scan, above); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (; (int)x <= scanline.Length - Vector.Count;) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector residual = scan - above; - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (; x < (uint)scanline.Length;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Average traversal. - /// - public static void EncodeAverage( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - uint bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 3; - - nuint x = 0; - - for (; x < bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - Vector256 allBitsSet = Avx2.CompareEqual(accumulator, accumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 average = Avx2.Xor( - Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), - allBitsSet); - - Vector256 residual = Avx2.Subtract(scan, average); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Sse2.IsSupported) - { - Vector128 zero = Vector128.Zero; - Vector128 accumulator = Vector128.Zero; - Vector128 allBitsSet = Sse2.CompareEqual(accumulator, accumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count) - { - Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector128 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector128 average = Sse2.Xor( - Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), - allBitsSet); - - Vector128 residual = Sse2.Subtract(scan, average); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector128.Count; - - Vector128 absolute; - - if (Ssse3.IsSupported) - { - absolute = Ssse3.Abs(residual.AsSByte()); - } - else - { - Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), residual.AsSByte()); - absolute = Sse2.Xor(Sse2.Add(residual.AsSByte(), mask), mask).AsByte(); - } - - accumulator = Sse2.Add( - accumulator, - Sse2.SumAbsoluteDifferences(absolute, zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - - for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - ((left + above) >> 1)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Paeth traversal. - /// - public static void EncodePaeth( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - int bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 4; - - nuint x = 0; - - for (; x < (uint)bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); - Vector256 residual = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); - Vector residual = scan - PaethPredictor(left, above, upperLeft); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref previousBaseRef, x); - byte upperLeft = Unsafe.Add(ref previousBaseRef, xLeft); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Selects the scalar Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) - { - int p = left + above - upperLeft; - int distanceLeft = Numerics.Abs(p - left); - int distanceAbove = Numerics.Abs(p - above); - int distanceUpperLeft = Numerics.Abs(p - upperLeft); - - if (distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft) - { - return left; - } - - return distanceAbove <= distanceUpperLeft ? above : upperLeft; - } - - /// - /// Selects the AVX2 Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PaethPredictor( - Vector256 left, - Vector256 above, - Vector256 upperLeft) - { - Vector256 zero = Vector256.Zero; - Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); - Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); - Vector256 distanceLeft = - Avx2.Or(Avx2.SubtractSaturate(upperLeft, above), aboveMinusUpper); - - Vector256 distanceAbove = - Avx2.Or(Avx2.SubtractSaturate(upperLeft, left), leftMinusUpper); - - Vector256 sameDirection = Avx2.CompareEqual( - Avx2.CompareEqual(aboveMinusUpper, zero), - Avx2.CompareEqual(leftMinusUpper, zero)); - - Vector256 distanceUpper = Avx2.Or( - sameDirection, - Avx2.Or( - Avx2.SubtractSaturate(distanceAbove, distanceLeft), - Avx2.SubtractSaturate(distanceLeft, distanceAbove))); - - Vector256 minimumAboveUpper = Avx2.Min(distanceUpper, distanceAbove); - Vector256 aboveOrUpper = Avx2.BlendVariable( - upperLeft, - above, - Avx2.CompareEqual(minimumAboveUpper, distanceAbove)); - - return Avx2.BlendVariable( - aboveOrUpper, - left, - Avx2.CompareEqual(Avx2.Min(minimumAboveUpper, distanceLeft), distanceLeft)); - } - - /// - /// Selects the portable vector Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor( - Vector left, - Vector above, - Vector upperLeft) - { - Vector.Widen(left, out Vector leftLow, out Vector leftHigh); - Vector.Widen(above, out Vector aboveLow, out Vector aboveHigh); - Vector.Widen(upperLeft, out Vector upperLow, out Vector upperHigh); - - Vector lower = PaethPredictor( - Vector.AsVectorInt16(leftLow), - Vector.AsVectorInt16(aboveLow), - Vector.AsVectorInt16(upperLow)); - - Vector upper = PaethPredictor( - Vector.AsVectorInt16(leftHigh), - Vector.AsVectorInt16(aboveHigh), - Vector.AsVectorInt16(upperHigh)); - - return Vector.AsVectorByte(Vector.Narrow(lower, upper)); - } - - /// - /// Selects the portable widened Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor( - Vector left, - Vector above, - Vector upperLeft) - { - Vector p = left + above - upperLeft; - Vector distanceLeft = Vector.Abs(p - left); - Vector distanceAbove = Vector.Abs(p - above); - Vector distanceUpper = Vector.Abs(p - upperLeft); - - Vector chooseLeft = Vector.BitwiseAnd( - Vector.LessThanOrEqual(distanceLeft, distanceAbove), - Vector.LessThanOrEqual(distanceLeft, distanceUpper)); - - return Vector.ConditionalSelect( - chooseLeft, - left, - Vector.ConditionalSelect( - Vector.LessThanOrEqual(distanceAbove, distanceUpper), - above, - upperLeft)); - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs deleted file mode 100644 index 14802f590..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class AddSpan -{ - private byte[] scalarValues = null!; - private byte[] tensorValues = null!; - private byte[] addends = null!; - - /// - /// Gets or sets the number of values to add. - /// - [Params(32, 257, 2048)] - public int Length { get; set; } - - /// - /// Creates equivalent deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.scalarValues = new byte[this.Length]; - this.tensorValues = new byte[this.Length]; - this.addends = new byte[this.Length]; - - for (int i = 0; i < this.Length; i++) - { - byte value = (byte)((i * 17) + 31); - this.scalarValues[i] = value; - this.tensorValues[i] = value; - this.addends[i] = (byte)((i * 29) + 7); - } - } - - /// - /// Adds the values with a scalar loop. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark(Baseline = true)] - public byte Scalar() - { - for (int i = 0; i < this.scalarValues.Length; i++) - { - this.scalarValues[i] += this.addends[i]; - } - - return this.scalarValues[0]; - } - - /// - /// Adds the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark] - public byte TensorPipeline() - { - TensorPrimitives_.Add(this.tensorValues, this.addends, this.tensorValues); - return this.tensorValues[0]; - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs deleted file mode 100644 index bc11fbaff..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class NormalizeSpan -{ - private float[] scalarValues = null!; - private float[] tensorValues = null!; - - /// - /// Gets or sets the number of values to normalize. - /// - [Params(7, 32, 257, 2048)] - public int Length { get; set; } - - /// - /// Creates equivalent deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.scalarValues = new float[this.Length]; - this.tensorValues = new float[this.Length]; - - for (int i = 0; i < this.scalarValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.scalarValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Normalizes the values with a scalar loop. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Scalar() - { - for (int i = 0; i < this.scalarValues.Length; i++) - { - this.scalarValues[i] /= 4096F; - } - - return this.scalarValues[0]; - } - - /// - /// Normalizes the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark] - public float TensorPipeline() - { - Numerics.Normalize(this.tensorValues, 4096F); - return this.tensorValues[0]; - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs new file mode 100644 index 000000000..855d5644c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +/// +/// Exposes every floating-point tensor compatibility operation for assembly inspection. +/// +[Config(typeof(Config.Analysis))] +public class TensorPrimitivesAssembly +{ + private const int Count = 2048; + + private readonly float[] x = new float[Count]; + private readonly float[] y = new float[Count]; + private readonly float[] destination = new float[Count]; + + /// + /// Populates the input spans with deterministic non-uniform values. + /// + [GlobalSetup] + public void Setup() + { + for (int i = 0; i < Count; i++) + { + this.x[i] = ((i * 17) % 251) + 1; + this.y[i] = ((i * 29) % 251) + 1; + } + } + + /// + /// Adds two floating-point spans. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Add() + { + TensorPrimitives_.Add(this.x, this.y, this.destination); + return this.destination[0]; + } + + /// + /// Clamps a floating-point span between scalar bounds. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Clamp() + { + TensorPrimitives_.Clamp(this.x, 64F, 128F, this.destination); + return this.destination[0]; + } + + /// + /// Divides a floating-point span by a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Divide() + { + TensorPrimitives_.Divide(this.x, 4096F, this.destination); + return this.destination[0]; + } + + /// + /// Computes the element-wise maximum of a floating-point span and a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Max() + { + TensorPrimitives_.Max(this.x, 64F, this.destination); + return this.destination[0]; + } + + /// + /// Multiplies a floating-point span by a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Multiply() + { + TensorPrimitives_.Multiply(this.x, 0.5F, this.destination); + return this.destination[0]; + } +} + +/// +/// Exposes integral addition specializations for assembly inspection. +/// +/// The integral element type. +[Config(typeof(Config.Analysis))] +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +public class TensorPrimitivesIntegralAddAssembly + where T : unmanaged, INumber +{ + private const int Count = 2048; + + private readonly T[] x = new T[Count]; + private readonly T[] y = new T[Count]; + private readonly T[] destination = new T[Count]; + + /// + /// Populates the input spans with deterministic non-uniform values. + /// + [GlobalSetup] + public void Setup() + { + for (int i = 0; i < Count; i++) + { + this.x[i] = T.CreateTruncating((i * 17) + 31); + this.y[i] = T.CreateTruncating((i * 29) + 7); + } + } + + /// + /// Adds two integral spans. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public T Add() + { + TensorPrimitives_.Add(this.x, this.y, this.destination); + return this.destination[0]; + } +} + +/// +/// Exposes integral clamp specializations for assembly inspection. +/// +/// The integral element type. +[Config(typeof(Config.Analysis))] +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +[GenericTypeArguments(typeof(int))] +public class TensorPrimitivesIntegralClampAssembly + where T : unmanaged, INumber +{ + private const int Count = 2048; + + private readonly T[] source = new T[Count]; + private readonly T[] destination = new T[Count]; + private T min; + private T max; + + /// + /// Populates the input span and scalar bounds with deterministic values. + /// + [GlobalSetup] + public void Setup() + { + this.min = T.CreateTruncating(64); + this.max = T.CreateTruncating(128); + + for (int i = 0; i < Count; i++) + { + this.source[i] = T.CreateTruncating((i * 31) % 257); + } + } + + /// + /// Clamps an integral span between scalar bounds. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public T Clamp() + { + TensorPrimitives_.Clamp(this.source, this.min, this.max, this.destination); + return this.destination[0]; + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs deleted file mode 100644 index 08ac3d09b..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs +++ /dev/null @@ -1,813 +0,0 @@ -// 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 BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -#pragma warning disable SA1649 // File name should match first type name -public class TensorPrimitivesJpegMultiplyAssemblyComparison -#pragma warning restore SA1649 // File name should match first type name -{ - private readonly float multiplier = -1F; - private float[] legacyValues = null!; - private float[] tensorValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new float[256]; - this.tensorValues = new float[256]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Multiplies the row with the retired JPEG AVX pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyMultiply(this.legacyValues, this.multiplier); - return this.legacyValues[0]; - } - - /// - /// Multiplies the row with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - TensorPrimitives_.Multiply(this.tensorValues, this.multiplier, this.tensorValues); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired JPEG multiplication loop for assembly comparison. - /// - /// The row to multiply. - /// The scalar multiplier. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyMultiply(Span target, float multiplier) - { - ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - nuint count = (uint)target.Length / (uint)Vector256.Count; - Vector256 multiplierVector = Vector256.Create(multiplier); - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVector, i) = Avx.Multiply(Unsafe.Add(ref targetVector, i), multiplierVector); - } - } -} - -public class TensorPrimitivesNormalizeAssemblyComparison -{ - private readonly float divisor = -1F; - private float[] legacyValues = null!; - private float[] tensorValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new float[7]; - this.tensorValues = new float[7]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Normalizes the values with the retired fixed-width pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyNormalize(this.legacyValues, this.divisor); - return this.legacyValues[0]; - } - - /// - /// Normalizes the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Numerics.Normalize(this.tensorValues, this.divisor); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired normalization loop for assembly comparison. - /// - /// The values to normalize. - /// The scalar divisor. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyNormalize(Span span, float sum) - { - ref float start = ref MemoryMarshal.GetReference(span); - ref float vectorEnd = ref Unsafe.Add(ref start, span.Length & ~7); - Vector256 sum256 = Vector256.Create(sum); - - while (Unsafe.IsAddressLessThan(ref start, ref vectorEnd)) - { - Unsafe.As>(ref start) /= sum256; - start = ref Unsafe.Add(ref start, (nuint)8); - } - - if ((span.Length & 7) >= 4) - { - Unsafe.As>(ref start) /= sum256.GetLower(); - start = ref Unsafe.Add(ref start, (nuint)4); - } - - ref float end = ref Unsafe.Add(ref start, span.Length & 3); - - while (Unsafe.IsAddressLessThan(ref start, ref end)) - { - start /= sum; - start = ref Unsafe.Add(ref start, (nuint)1); - } - } -} - -public class TensorPrimitivesUInt32AssemblyComparison -{ - private uint[] x = null!; - private uint[] y = null!; - private uint[] legacyDestination = null!; - private uint[] tensorDestination = null!; - - /// - /// Creates deterministic histogram inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new uint[2048]; - this.y = new uint[2048]; - this.legacyDestination = new uint[2048]; - this.tensorDestination = new uint[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = (uint)((i * 17) + 31); - this.y[i] = (uint)((i * 29) + 7); - } - } - - /// - /// Adds histogram bins with the retired four-vector AVX2 pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public uint Legacy() - { - LegacyAdd(this.x, this.y, this.legacyDestination); - return this.legacyDestination[0]; - } - - /// - /// Adds histogram bins with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public uint Tensor() - { - TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); - return this.tensorDestination[0]; - } - - /// - /// Reproduces the retired WebP histogram addition loop for assembly comparison. - /// - /// The first histogram. - /// The second histogram. - /// The destination receiving the sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) - { - ref uint xRef = ref MemoryMarshal.GetReference(x); - ref uint yRef = ref MemoryMarshal.GetReference(y); - ref uint destinationRef = ref MemoryMarshal.GetReference(destination); - - nuint index = 0; - - do - { - Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, index)); - Vector256 x1 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 8)); - Vector256 x2 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 16)); - Vector256 x3 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 24)); - Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, index)); - Vector256 y1 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 8)); - Vector256 y2 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 16)); - Vector256 y3 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 24)); - - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index)) = Avx2.Add(x0, y0); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 8)) = Avx2.Add(x1, y1); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 16)) = Avx2.Add(x2, y2); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 24)) = Avx2.Add(x3, y3); - index += 32; - } - while (index <= (uint)x.Length - 32); - - for (int i = (int)index; i < x.Length; i++) - { - destination[i] = x[i] + y[i]; - } - } -} - -public class TensorPrimitivesByteAssemblyComparison -{ - private byte[] x = null!; - private byte[] y = null!; - private byte[] legacyDestination = null!; - private byte[] tensorDestination = null!; - - /// - /// Creates deterministic byte inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new byte[2048]; - this.y = new byte[2048]; - this.legacyDestination = new byte[2048]; - this.tensorDestination = new byte[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = (byte)((i * 17) + 31); - this.y[i] = (byte)((i * 29) + 7); - } - } - - /// - /// Adds bytes with the retired WebP AVX2 pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public byte Legacy() - { - LegacyAdd(this.x, this.y, this.legacyDestination); - return this.legacyDestination[0]; - } - - /// - /// Adds bytes with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public byte Tensor() - { - TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); - return this.tensorDestination[0]; - } - - /// - /// Reproduces the retired WebP byte addition loop for assembly comparison. - /// - /// The first input. - /// The second input. - /// The destination receiving modulo-256 sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) - { - ref byte xRef = ref MemoryMarshal.GetReference(x); - ref byte yRef = ref MemoryMarshal.GetReference(y); - ref byte destinationRef = ref MemoryMarshal.GetReference(destination); - - nuint i; - int maxPosition = x.Length & ~31; - - for (i = 0; i < (uint)maxPosition; i += 32) - { - Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); - Vector256 result = x0.AsByte() + y0.AsByte(); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, i)) = result; - } - - for (; i < (uint)x.Length; i++) - { - Unsafe.Add(ref destinationRef, i) = (byte)(Unsafe.Add(ref xRef, i) + Unsafe.Add(ref yRef, i)); - } - } -} - -public class TensorPrimitivesSingleAddAssemblyComparison -{ - private float[] legacyTarget = null!; - private float[] tensorTarget = null!; - private float[] source = null!; - - /// - /// Creates deterministic JPEG row inputs. - /// - [GlobalSetup] - public void Setup() - { - this.legacyTarget = new float[2048]; - this.tensorTarget = new float[2048]; - this.source = new float[2048]; - - for (int i = 0; i < this.source.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyTarget[i] = value; - this.tensorTarget[i] = value; - this.source[i] = ((i * 29) % 31) - 15; - } - } - - /// - /// Adds JPEG row values with the retired AVX pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyAdd(this.legacyTarget, this.source); - return this.legacyTarget[0]; - } - - /// - /// Adds JPEG row values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - TensorPrimitives_.Add(this.tensorTarget, this.source, this.tensorTarget); - return this.tensorTarget[0]; - } - - /// - /// Reproduces the retired JPEG row addition loop for assembly comparison. - /// - /// The destination row. - /// The row added to the destination. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(Span target, ReadOnlySpan source) - { - ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector256 sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - nuint count = (uint)source.Length / (uint)Vector256.Count; - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVector, i) = Avx.Add(Unsafe.Add(ref targetVector, i), Unsafe.Add(ref sourceVector, i)); - } - } -} - -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(int))] -[GenericTypeArguments(typeof(float))] -[GenericTypeArguments(typeof(double))] -public class TensorPrimitivesClampAssemblyComparison - where T : unmanaged, INumber -{ - private T[] legacyValues = null!; - private T[] tensorValues = null!; - private T min; - private T max; - - /// - /// Creates deterministic clamp inputs for the current element type. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new T[2048]; - this.tensorValues = new T[2048]; - this.min = T.CreateTruncating(64); - this.max = T.CreateTruncating(128); - - for (int i = 0; i < this.legacyValues.Length; i++) - { - T value = T.CreateTruncating((i * 31) % 257); - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Clamps values with the retired pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Legacy() - { - LegacyClamp(this.legacyValues, this.min, this.max); - return this.legacyValues[0]; - } - - /// - /// Clamps values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Tensor() - { - TensorPrimitives_.Clamp(this.tensorValues, this.min, this.max, this.tensorValues); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired clamp pipeline for assembly comparison. - /// - /// The values to clamp. - /// The inclusive lower bound. - /// The inclusive upper bound. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyClamp(Span span, T min, T max) - { - int remainder = Numerics.ModuloP2(span.Length, Vector.Count); - int adjustedCount = span.Length - remainder; - - if (adjustedCount > 0) - { - Vector vectorMin = new(min); - Vector vectorMax = new(max); - nint vectorCount = (nint)(uint)adjustedCount / Vector.Count; - nint remainingVectors = Numerics.Modulo4(vectorCount); - nint unrolledVectors = vectorCount - remainingVectors; - - ref Vector current0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - ref Vector current1 = ref Unsafe.Add(ref current0, 1); - ref Vector current2 = ref Unsafe.Add(ref current0, 2); - ref Vector current3 = ref Unsafe.Add(ref current0, 3); - ref Vector end = ref Unsafe.Add(ref current0, unrolledVectors); - - while (Unsafe.IsAddressLessThan(ref current0, ref end)) - { - current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); - current1 = Vector.Min(Vector.Max(vectorMin, current1), vectorMax); - current2 = Vector.Min(Vector.Max(vectorMin, current2), vectorMax); - current3 = Vector.Min(Vector.Max(vectorMin, current3), vectorMax); - - current0 = ref Unsafe.Add(ref current0, 4); - current1 = ref Unsafe.Add(ref current1, 4); - current2 = ref Unsafe.Add(ref current2, 4); - current3 = ref Unsafe.Add(ref current3, 4); - } - - if (remainingVectors > 0) - { - current0 = ref end; - end = ref Unsafe.Add(ref end, remainingVectors); - - while (Unsafe.IsAddressLessThan(ref current0, ref end)) - { - current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); - current0 = ref Unsafe.Add(ref current0, 1); - } - } - } - - for (int i = adjustedCount; i < span.Length; i++) - { - T value = span[i]; - span[i] = value > max ? max : value < min ? min : value; - } - } -} - -public class TensorPrimitivesIccMaxAssemblyComparison -{ - private Vector4[] legacyValues = null!; - private Vector4[] tensorValues = null!; - - /// - /// Creates deterministic ICC values containing positive and negative channels. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new Vector4[512]; - this.tensorValues = new Vector4[512]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) - 125; - Vector4 vector = new(value, value + 1, value - 1, value + 2); - this.legacyValues[i] = vector; - this.tensorValues[i] = vector; - } - } - - /// - /// Clips negative channels with the retired ICC pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - for (int i = 0; i < this.legacyValues.Length; i++) - { - this.legacyValues[i] = Vector4.Max(this.legacyValues[i], Vector4.Zero); - } - - return this.legacyValues[0].X; - } - - /// - /// Clips negative channels with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Span values = MemoryMarshal.Cast(this.tensorValues.AsSpan()); - TensorPrimitives_.Max(values, 0F, values); - return values[0]; - } -} - -public class TensorPrimitivesIccMultiplyAssemblyComparison -{ - private readonly float multiplier = 65280F / 65535F; - private Vector4[] source = null!; - private Vector4[] legacyDestination = null!; - private Vector4[] tensorDestination = null!; - - /// - /// Creates deterministic ICC inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.source = new Vector4[512]; - this.legacyDestination = new Vector4[512]; - this.tensorDestination = new Vector4[512]; - - for (int i = 0; i < this.source.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.source[i] = new Vector4(value, value + 1, value + 2, value + 3); - } - } - - /// - /// Multiplies ICC channels with the retired pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - Span source = MemoryMarshal.Cast(this.source.AsSpan()); - Span destination = MemoryMarshal.Cast(this.legacyDestination.AsSpan()); - ref Vector sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector destinationVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector scale = new(this.multiplier); - nuint count = (uint)source.Length / (uint)Vector.Count; - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref destinationVector, i) = Unsafe.Add(ref sourceVector, i) * scale; - } - - return destination[0]; - } - - /// - /// Multiplies ICC channels with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Span source = MemoryMarshal.Cast(this.source.AsSpan()); - Span destination = MemoryMarshal.Cast(this.tensorDestination.AsSpan()); - TensorPrimitives_.Multiply(source, this.multiplier, destination); - return destination[0]; - } -} - -#if NET10_0_OR_GREATER -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(float))] -public class TensorPrimitivesRuntimeAddAssemblyComparison - where T : unmanaged, INumber -{ - private T[] x = null!; - private T[] y = null!; - private T[] compatibilityDestination = null!; - private T[] runtimeDestination = null!; - - /// - /// Creates deterministic inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new T[2048]; - this.y = new T[2048]; - this.compatibilityDestination = new T[2048]; - this.runtimeDestination = new T[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = T.CreateTruncating((i * 17) + 31); - this.y[i] = T.CreateTruncating((i * 29) + 7); - } - } - - /// - /// Adds values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Compatibility() - { - TensorPrimitives_.Add(this.x, this.y, this.compatibilityDestination); - return this.compatibilityDestination[0]; - } - - /// - /// Adds values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Runtime() - { - System.Numerics.Tensors.TensorPrimitives.Add(this.x, this.y, this.runtimeDestination); - return this.runtimeDestination[0]; - } -} - -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(int))] -[GenericTypeArguments(typeof(float))] -[GenericTypeArguments(typeof(double))] -public class TensorPrimitivesRuntimeClampAssemblyComparison - where T : unmanaged, INumber -{ - private T[] compatibilityValues = null!; - private T[] runtimeValues = null!; - private T min; - private T max; - - /// - /// Creates deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.compatibilityValues = new T[2048]; - this.runtimeValues = new T[2048]; - this.min = T.CreateTruncating(64); - this.max = T.CreateTruncating(128); - - for (int i = 0; i < this.compatibilityValues.Length; i++) - { - T value = T.CreateTruncating((i * 31) % 257); - this.compatibilityValues[i] = value; - this.runtimeValues[i] = value; - } - } - - /// - /// Clamps values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Compatibility() - { - TensorPrimitives_.Clamp(this.compatibilityValues, this.min, this.max, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Clamps values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Runtime() - { - System.Numerics.Tensors.TensorPrimitives.Clamp(this.runtimeValues, this.min, this.max, this.runtimeValues); - return this.runtimeValues[0]; - } -} - -public class TensorPrimitivesRuntimeSingleScalarAssemblyComparison -{ - private readonly float scalar = -1F; - private float[] compatibilityValues = null!; - private float[] runtimeValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.compatibilityValues = new float[2048]; - this.runtimeValues = new float[2048]; - - for (int i = 0; i < this.compatibilityValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.compatibilityValues[i] = value; - this.runtimeValues[i] = value; - } - } - - /// - /// Divides values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityDivide() - { - TensorPrimitives_.Divide(this.compatibilityValues, this.scalar, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Divides values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeDivide() - { - System.Numerics.Tensors.TensorPrimitives.Divide(this.runtimeValues, this.scalar, this.runtimeValues); - return this.runtimeValues[0]; - } - - /// - /// Computes maximum values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityMax() - { - TensorPrimitives_.Max(this.compatibilityValues, 0F, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Computes maximum values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeMax() - { - System.Numerics.Tensors.TensorPrimitives.Max(this.runtimeValues, 0F, this.runtimeValues); - return this.runtimeValues[0]; - } - - /// - /// Multiplies values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityMultiply() - { - TensorPrimitives_.Multiply(this.compatibilityValues, this.scalar, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Multiplies values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeMultiply() - { - System.Numerics.Tensors.TensorPrimitives.Multiply(this.runtimeValues, this.scalar, this.runtimeValues); - return this.runtimeValues[0]; - } -} -#endif diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs index 0eb7b9ccd..d671117b6 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs @@ -2,16 +2,13 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; /// -/// Compares operator-driven affine vector transforms with the duplicated traversals they replace. +/// Measures the operator-driven affine vector transforms. /// [Config(typeof(Config.Short))] public class Vector4AffineTransform @@ -21,7 +18,6 @@ public class Vector4AffineTransform private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); private Vector4[] current; - private Vector4[] baseline; /// /// Gets or sets the number of vectors transformed by each invocation. @@ -30,7 +26,7 @@ public class Vector4AffineTransform public int Count { get; set; } /// - /// Creates identical non-uniform buffers for the current and baseline traversals. + /// Creates a non-uniform input buffer. /// [GlobalSetup] public void Setup() @@ -41,167 +37,19 @@ public class Vector4AffineTransform { this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); } - - this.baseline = [.. this.current]; } /// /// Executes the operator-driven multiply-then-add traversal. /// [Benchmark] - public void CurrentMultiplyThenAdd() + public void MultiplyThenAdd() => Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset); - /// - /// Executes the duplicated multiply-then-add traversal. - /// - [Benchmark(Baseline = true)] - public void BaselineMultiplyThenAdd() - => BaselineMultiplyThenAdd(this.baseline, Multiplier, Offset); - /// /// Executes the operator-driven add-then-divide traversal. /// [Benchmark] - public void CurrentAddThenDivide() + public void AddThenDivide() => Vector4Converters.AddThenDivide(this.current, Offset, Divisor); - - /// - /// Executes the duplicated add-then-divide traversal. - /// - [Benchmark] - public void BaselineAddThenDivide() - => BaselineAddThenDivide(this.baseline, Offset, Divisor); - - /// - /// Retains the multiply-then-add traversal being replaced for direct measurement. - /// - /// The vectors to transform. - /// The component-wise multiplier. - /// The component-wise offset. - internal static void BaselineMultiplyThenAdd(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; - } - - for (; index < vectors.Length; index++) - { - ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); - vector = (vector * multiplier) + offset; - } - } - - /// - /// Retains the add-then-divide traversal being replaced for direct measurement. - /// - /// The vectors to transform. - /// The component-wise offset. - /// The component-wise divisor. - internal static void BaselineAddThenDivide(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; - } - - for (; index < vectors.Length; index++) - { - ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); - vector = (vector + offset) / divisor; - } - } } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs index fcecf1306..1b5767ebd 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs @@ -56,18 +56,4 @@ public class Vector4AffineTransformAssembly [Benchmark] public void AddThenDivide() => Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor); - - /// - /// Executes the multiply-then-add traversal being replaced for assembly comparison. - /// - [Benchmark] - public void BaselineMultiplyThenAdd() - => Vector4AffineTransform.BaselineMultiplyThenAdd(this.vectors, Multiplier, Offset); - - /// - /// Executes the add-then-divide traversal being replaced for assembly comparison. - /// - [Benchmark] - public void BaselineAddThenDivide() - => Vector4AffineTransform.BaselineAddThenDivide(this.vectors, Offset, Divisor); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index c11456014..dae2d3c3f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,12 +2,9 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.ColorProfiles; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -15,37 +12,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg; public class JpegColorConverterTests { private const float MaxColorChannelValue = 255F; + private const float ColorProfileTolerance = 0.1F / MaxColorChannelValue; + private const float ToRgbTolerance = 0.0001F; + private const float FromRgbTolerance = 0.01F; - private const float Precision = 0.1F / 255; - - private const int TestBufferLength = 40; - - private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: Precision); - - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - - public JpegColorConverterTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } + // Independent model checks compare normalized colors at one tenth of a byte-domain sample. + private static readonly ApproximateColorProfileComparer ColorSpaceComparer = + new(epsilon: ColorProfileTolerance); + /// + /// Verifies that unsupported color spaces are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidColorSpace() { const JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); } + /// + /// Verifies that unsupported JPEG sample precisions are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidPrecision() { - // Valid precisions: 8 & 12 bit const int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } /// - /// Verifies that each supported color space and precision resolves to an available converter. + /// Verifies that every supported color-space and precision pair resolves to the shared operator converter. /// /// The JPEG color space. /// The JPEG sample precision. @@ -68,312 +66,137 @@ public class JpegColorConverterTests { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - Assert.NotNull(converter); Assert.True(converter.IsAvailable); Assert.Equal(colorSpace, converter.ColorSpace); Assert.Equal(precision, converter.Precision); } - [Fact] - public void GetConverterReturnsCorrectConverterWithRgbColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithGrayScaleColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithCmykColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYCbCrColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYcckColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - /// - /// Verifies that TIFF color spaces resolve to their closed shared converter types. + /// Verifies that the converter factory closes the shared traversal over the matching color-model operator. /// - /// The TIFF JPEG color space. + /// The JPEG color space. /// The expected closed converter type. [Theory] + [InlineData( + JpegColorSpace.Grayscale, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.RGB, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.Cmyk, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.YCbCr, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.Ycck, + typeof(JpegColorConverterBase.JpegColorConverter))] [InlineData( JpegColorSpace.TiffCmyk, typeof(JpegColorConverterBase.JpegColorConverter))] [InlineData( JpegColorSpace.TiffYccK, typeof(JpegColorConverterBase.JpegColorConverter))] - internal void GetConverterReturnsCorrectConverterWithTiffColorSpace(JpegColorSpace colorSpace, Type expectedType) + internal void GetConverterReturnsClosedOperatorConverter(JpegColorSpace colorSpace, Type expectedType) { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); Assert.Equal(expectedType, converter.GetType()); } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 1)] - [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.Cmyk, 4)] - [InlineData(JpegColorSpace.RGB, 3)] - [InlineData(JpegColorSpace.YCbCr, 3)] - internal void ConvertToRgbWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) - { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - ValidateConversionToRgb( - converter, - componentCount, - 1); - } - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - /// - /// Verifies YCbCr equivalence around every scalar and SIMD width boundary. + /// Verifies the replacement converter against independent definitions of the established JPEG color models. /// - /// The number of samples to convert. - /// The JPEG sample precision. - [Theory] - [InlineData(1, 8)] - [InlineData(3, 12)] - [InlineData(4, 8)] - [InlineData(7, 12)] - [InlineData(8, 8)] - [InlineData(15, 12)] - [InlineData(16, 8)] - [InlineData(31, 12)] - [InlineData(32, 8)] - [InlineData(40, 12)] - [InlineData(64, 8)] - [InlineData(128, 12)] - public void YCbCrOperatorMatchesScalarForAllVectorBoundaries(int length, int precision) - { - JpegColorConverterBase converter = - new JpegColorConverterBase.JpegColorConverter(precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); - - ValidateConversionToRgb(converter, baseline, length, 3, precision); - ValidateConversionFromRgb(converter, baseline, length, 3, precision); - } - - /// - /// Verifies that the YCbCr operator retains scalar behavior when hardware intrinsics are disabled. - /// - [Fact] - public void YCbCrOperatorMatchesScalarWithoutHardwareIntrinsics() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - - /// - /// Verifies converter equivalence around every scalar and SIMD width boundary. - /// - /// The color space under test. - /// The number of component planes written by the converter. + /// The JPEG color space. + /// The number of component planes owned by the color model. [Theory] [InlineData(JpegColorSpace.Grayscale, 1)] [InlineData(JpegColorSpace.RGB, 3)] [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.YCbCr, 3)] [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.TiffCmyk, 4)] - internal void OperatorsMatchScalarAcrossEveryWidthBoundary(JpegColorSpace colorSpace, int componentCount) + internal void ConvertToRgbMatchesColorModelDefinition(JpegColorSpace colorSpace, int componentCount) { + const int length = 128; + JpegColorConverterBase.ComponentValues source = CreateRandomValues(length, componentCount, 8); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, 8); JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - JpegColorConverterBase baseline = colorSpace switch - { - JpegColorSpace.Grayscale => new JpegColorConverterBase.GrayScaleScalar(8), - JpegColorSpace.RGB => new JpegColorConverterBase.RgbScalar(8), - JpegColorSpace.Cmyk => new JpegColorConverterBase.CmykScalar(8), - JpegColorSpace.Ycck => new JpegColorConverterBase.YccKScalar(8), - JpegColorSpace.TiffCmyk => new JpegColorConverterBase.TiffCmykScalar(8), - _ => throw new InvalidOperationException(), - }; - // These lengths exercise every point immediately below, at, and above the supported SIMD widths. - int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; + converter.ConvertToRgbInPlace(actual); - foreach (int length in lengths) + for (int i = 0; i < length; i++) { - ValidateConversionToRgb(converter, baseline, length, componentCount, 8); - ValidateConversionFromRgb(converter, baseline, length, componentCount, 8); + AssertColorModelDefinition(colorSpace, source, actual, i); } } /// - /// Verifies TIFF YCCK decoding around every scalar and SIMD width boundary. + /// Verifies the adaptive traversal against each operator's scalar definition around every SIMD boundary. /// + /// The JPEG color space. + /// The number of component planes owned by the color model. /// The JPEG sample precision. [Theory] - [InlineData(8)] - [InlineData(12)] - public void TiffYccKOperatorToRgbMatchesScalarAcrossEveryWidthBoundary(int precision) + [InlineData(JpegColorSpace.Grayscale, 1, 8)] + [InlineData(JpegColorSpace.Grayscale, 1, 12)] + [InlineData(JpegColorSpace.RGB, 3, 8)] + [InlineData(JpegColorSpace.RGB, 3, 12)] + [InlineData(JpegColorSpace.Cmyk, 4, 8)] + [InlineData(JpegColorSpace.Cmyk, 4, 12)] + [InlineData(JpegColorSpace.YCbCr, 3, 8)] + [InlineData(JpegColorSpace.YCbCr, 3, 12)] + [InlineData(JpegColorSpace.Ycck, 4, 8)] + [InlineData(JpegColorSpace.Ycck, 4, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] + [InlineData(JpegColorSpace.TiffYccK, 4, 8)] + [InlineData(JpegColorSpace.TiffYccK, 4, 12)] + internal void OperatorTraversalMatchesScalarDefinition( + JpegColorSpace colorSpace, + int componentCount, + int precision) { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.TiffYccKScalar(precision); - - // The combined lengths force scalar, 128-bit, 256-bit, and 512-bit work on capable hardware. - int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; - - foreach (int length in lengths) + switch (colorSpace) { - ValidateConversionToRgb(converter, baseline, length, 4, precision); + case JpegColorSpace.Grayscale: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.RGB: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Cmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.YCbCr: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Ycck: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffCmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffYccK: + ValidateOperator(componentCount, precision); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + break; } } /// - /// Verifies TIFF YCCK encoding against the canonical normalized color-profile conversion. + /// Verifies that the shared converter retains its scalar behavior when hardware intrinsics are disabled. + /// + [Fact] + public void OperatorTraversalMatchesScalarWithoutHardwareIntrinsics() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunWithoutHardwareIntrinsics, + HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies TIFF YccK encoding against the canonical normalized color-profile conversion. /// [Fact] public void TiffYccKOperatorFromRgbMatchesColorProfileDefinition() @@ -381,7 +204,6 @@ public class JpegColorConverterTests const int maximumLength = 40; const float maximumValue = 255F; const float halfValue = 128F; - const float tolerance = 0.0001F; float[] rSeed = [0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 64, 192]; float[] gSeed = [0, 255, 0, 255, 0, 127, 160, 16, 0, 255, 0, 255, 0, 127, 160, 16, 255, 0, 128, 96]; float[] bSeed = [0, 255, 0, 0, 255, 127, 224, 80, 255, 0, 255, 0, 0, 127, 224, 80, 0, 255, 192, 32]; @@ -390,7 +212,7 @@ public class JpegColorConverterTests float[] b = new float[maximumLength]; JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, 8); - // Repeating the color set provides enough lanes to exercise every SIMD width and each mixed-width tail. + // Repeating the color set provides enough lanes to exercise every SIMD width and mixed-width tail. rSeed.CopyTo(r, 0); rSeed.CopyTo(r, rSeed.Length); gSeed.CopyTo(g, 0); @@ -398,8 +220,6 @@ public class JpegColorConverterTests bSeed.CopyTo(b, 0); bSeed.CopyTo(b, bSeed.Length); - // The normalized color-profile implementation is the canonical definition; JPEG stores each result - // in the configured integer sample domain. ColorProfileConverter reference = new(); int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40]; @@ -418,659 +238,302 @@ public class JpegColorConverterTests Rgb rgb = new(r[i] / maximumValue, g[i] / maximumValue, b[i] / maximumValue); YccK expected = reference.Convert(rgb); - Assert.Equal(expected.Y * maximumValue, y[i], tolerance); + Assert.Equal(expected.Y * maximumValue, y[i], ToRgbTolerance); - // JPEG centers chroma on the integer sample midpoint (128 at 8-bit precision), whereas - // the color-profile definition centers normalized chroma exactly on 0.5. - Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], tolerance); - Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], tolerance); - Assert.Equal(expected.K * maximumValue, k[i], tolerance); + // JPEG centers chroma on the integer midpoint, while the color-profile definition uses exactly 0.5. + Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], ToRgbTolerance); + Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], ToRgbTolerance); + Assert.Equal(expected.K * maximumValue, k[i], ToRgbTolerance); } } } /// - /// Runs the YCbCr equivalence check in the feature-test process. + /// Runs the disabled-intrinsics scalar comparison inside the feature-test process. /// /// The unused feature-test argument. - private static void RunTest(string arg) + private static void RunWithoutHardwareIntrinsics(string arg) + => ValidateOperator(3, 8); + + /// + /// Checks one closed operator converter at every scalar and SIMD transition length. + /// + /// The color-model operator under test. + /// The number of component planes owned by the operator. + /// The JPEG sample precision. + private static void ValidateOperator(int componentCount, int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - const int length = 40; - const int precision = 8; JpegColorConverterBase converter = - new JpegColorConverterBase.JpegColorConverter(precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); - - ValidateConversionToRgb(converter, baseline, length, 3, precision); - ValidateConversionFromRgb(converter, baseline, length, 3, precision); - } - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.GrayScaleScalar(8), 1, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); + new JpegColorConverterBase.JpegColorConverter(precision); + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; - private void TestConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) - { - if (!converter.IsAvailable) + // Adjacent values around 4/8/16 lanes verify every prefix, mixed-width tail, and scalar remainder. + foreach (int length in lengths) { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; + ValidateConversionToRgb(converter, length, componentCount, precision); + ValidateConversionFromRgb(converter, length, componentCount, precision); } - - ValidateConversionToRgb( - converter, - componentCount, - seed, - baseLineConverter); } - private void TestConversionFromRgb( + /// + /// Compares the adaptive component-to-RGB traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of source component planes. + /// The JPEG sample precision. + private static void ValidateConversionToRgb( JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision) - { - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversionFromRgb( - converter, - componentCount, - seed, - baseLineConverter, - precision); - } - - private static JpegColorConverterBase.ComponentValues CreateRandomValues( int length, int componentCount, - int seed) + int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - Random rnd = new(seed); + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; - Buffer2D[] buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) + for (int i = 0; i < length; i++) { - float[] values = new float[length]; + ref float c0 = ref expected.Component0[i]; + ref float c1 = ref expected.Component1[i]; + ref float c2 = ref expected.Component2[i]; + float c3 = componentCount == 4 ? expected.Component3[i] : 0; - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } - - // no need to dispose when buffer is not array owner - Memory memory = new(values); - MemoryGroup source = MemoryGroup.Wrap(memory); - buffers[i] = new Buffer2D(source, values.Length, 1); + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scale); } - return new JpegColorConverterBase.ComponentValues(buffers, 0); - } - - private static float[] CreateRandomValues(int length, Random rnd) - { - float[] values = new float[length]; - - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } + converter.ConvertToRgbInPlace(actual); - return values; + // YCbCr conversion rounds in the integer sample domain before normalization. Fused SIMD arithmetic can + // cross a half-way boundary differently from the scalar expression, so allow one source-sample quantum + // in addition to the ordinary floating-point tolerance at both supported sample precisions. + float tolerance = scale + ToRgbTolerance; + CompareSequence(expected.Component0, actual.Component0, tolerance); + CompareSequence(expected.Component1, actual.Component1, tolerance); + CompareSequence(expected.Component2, actual.Component2, tolerance); } - private static void ValidateConversionToRgb( + /// + /// Compares the adaptive RGB-to-component traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of destination component planes. + /// The JPEG sample precision. + private static void ValidateConversionFromRgb( JpegColorConverterBase converter, + int length, int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) + int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues actual = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - - converter.ConvertToRgbInPlace(actual); - - for (int i = 0; i < TestBufferLength; i++) - { - Validate(converter.ColorSpace, original, actual, i); - } - - // Compare conversion result to a baseline, should be the scalar version. - if (baseLineConverter != null) + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + Random random = new(precision); + float[] r = CreateRandomValues(length, random); + float[] g = CreateRandomValues(length, random); + float[] b = CreateRandomValues(length, random); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; + + for (int i = 0; i < length; i++) { - JpegColorConverterBase.ComponentValues expected = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - baseLineConverter.ConvertToRgbInPlace(expected); - if (componentCount == 1) - { - Assert.True(expected.Component0.SequenceEqual(actual.Component0)); - } - - if (componentCount == 2) + TOperator.ConvertFromRgb( + r[i], + g[i], + b[i], + maximumValue, + halfValue, + scale, + out expected.Component0[i], + out float c1, + out float c2, + out float c3); + + if (componentCount >= 2) { - Assert.True(expected.Component1.SequenceEqual(actual.Component1)); + expected.Component1[i] = c1; } - if (componentCount == 3) + if (componentCount >= 3) { - Assert.True(expected.Component2.SequenceEqual(actual.Component2)); + expected.Component2[i] = c2; } if (componentCount == 4) { - Assert.True(expected.Component3.SequenceEqual(actual.Component3)); + expected.Component3[i] = c3; } } - } - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision = 4) - { - // arrange - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed); - Random rnd = new(seed); - float[] rLane = CreateRandomValues(TestBufferLength, rnd); - float[] gLane = CreateRandomValues(TestBufferLength, rnd); - float[] bLane = CreateRandomValues(TestBufferLength, rnd); - - // act - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - baseLineConverter.ConvertFromRgb(expected, rLane, gLane, bLane); - - // assert - if (componentCount == 1) - { - CompareSequenceWithTolerance(expected.Component0, actual.Component0, precision); - } + converter.ConvertFromRgb(actual, r, g, b); + CompareSequence(expected.Component0, actual.Component0, FromRgbTolerance); - if (componentCount == 2) + if (componentCount >= 2) { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, precision); + CompareSequence(expected.Component1, actual.Component1, FromRgbTolerance); } - if (componentCount == 3) + if (componentCount >= 3) { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, precision); + CompareSequence(expected.Component2, actual.Component2, FromRgbTolerance); } if (componentCount == 4) { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, precision); - } - } - - private static void CompareSequenceWithTolerance(Span expected, Span actual, int precision) - { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], precision: precision); - } - } - - /// - /// Compares two component planes using an absolute floating-point tolerance. - /// - /// The expected component values. - /// The actual component values. - /// The maximum permitted absolute difference. - private static void CompareSequenceWithTolerance(Span expected, Span actual, float tolerance) - { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], tolerance); + CompareSequence(expected.Component3, actual.Component3, FromRgbTolerance); } } /// - /// Compares component-to-RGB conversion with a scalar reference implementation. + /// Creates deterministic component planes in the configured JPEG sample domain. /// - /// The shared converter under test. - /// The scalar reference converter. - /// The number of samples to convert. - /// The number of source component planes. - /// The JPEG sample precision. - private static void ValidateConversionToRgb( - JpegColorConverterBase converter, - JpegColorConverterBase baseline, + /// The number of samples in each plane. + /// The number of independent component planes. + /// The JPEG sample precision and deterministic random seed. + /// The generated component planes. + private static JpegColorConverterBase.ComponentValues CreateRandomValues( int length, int componentCount, int precision) { - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); - - baseline.ConvertToRgbInPlace(expected); - converter.ConvertToRgbInPlace(actual); + Random random = new(precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float[] c0 = CreateRandomValues(length, random, maximumValue); + float[] c1 = componentCount >= 2 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c2 = componentCount >= 3 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c3 = componentCount == 4 ? CreateRandomValues(length, random, maximumValue) : []; - // SIMD multiply-add instructions can differ from the scalar expression by the final rounding bit. - CompareSequenceWithTolerance(expected.Component0, actual.Component0, 0.0001F); - CompareSequenceWithTolerance(expected.Component1, actual.Component1, 0.0001F); - CompareSequenceWithTolerance(expected.Component2, actual.Component2, 0.0001F); + return new JpegColorConverterBase.ComponentValues(componentCount, c0, c1, c2, c3); } /// - /// Compares RGB-to-component conversion with a scalar reference implementation. + /// Creates deterministic RGB samples in ImageSharp's byte-scaled encoder domain. /// - /// The shared converter under test. - /// The scalar reference converter. - /// The number of samples to convert. - /// The number of destination component planes. - /// The JPEG sample precision. - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - JpegColorConverterBase baseline, - int length, - int componentCount, - int precision) - { - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); - Random random = new(precision); - float[] rLane = CreateRandomValues(length, random); - float[] gLane = CreateRandomValues(length, random); - float[] bLane = CreateRandomValues(length, random); - - baseline.ConvertFromRgb(expected, rLane, gLane, bLane); - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - - // The generic traversal must preserve every plane owned by the closed color model. - CompareSequenceWithTolerance(expected.Component0, actual.Component0, 2); + /// The number of samples. + /// The deterministic random source. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random) + => CreateRandomValues(length, random, 255F); - if (componentCount >= 2) - { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, 2); - } + /// + /// Creates deterministic samples between zero and the supplied inclusive domain maximum. + /// + /// The number of samples. + /// The deterministic random source. + /// The upper bound of the sample domain. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random, float maximumValue) + { + float[] values = new float[length]; - if (componentCount >= 3) + for (int i = 0; i < values.Length; i++) { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, 2); + values[i] = random.NextSingle() * maximumValue; } - if (componentCount >= 4) - { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, 2); - } + return values; } - private static void Validate( + /// + /// Compares one converted sample with an independent definition of its JPEG color model. + /// + /// The JPEG color space. + /// The unmodified source component planes. + /// The converted RGB planes. + /// The sample index. + private static void AssertColorModelDefinition( JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues original, - in JpegColorConverterBase.ComponentValues result, - int i) + in JpegColorConverterBase.ComponentValues source, + in JpegColorConverterBase.ComponentValues actual, + int index) { + float c0 = source.Component0[index]; + float c1 = source.Component1[index]; + float c2 = source.Component2[index]; + float c3 = 0; + Rgb expected; + switch (colorSpace) { case JpegColorSpace.Grayscale: - ValidateGrayScale(original, result, i); + float luminance = c0 / MaxColorChannelValue; + expected = new Rgb(luminance, luminance, luminance); break; - case JpegColorSpace.Ycck: - ValidateYccK(original, result, i); + case JpegColorSpace.RGB: + expected = new Rgb( + c0 / MaxColorChannelValue, + c1 / MaxColorChannelValue, + c2 / MaxColorChannelValue); + break; case JpegColorSpace.Cmyk: - ValidateCmyk(original, result, i); - break; - case JpegColorSpace.RGB: - ValidateRgb(original, result, i); + c3 = source.Component3[index] / MaxColorChannelValue; + expected = new Rgb( + c0 * c3 / MaxColorChannelValue, + c1 * c3 / MaxColorChannelValue, + c2 * c3 / MaxColorChannelValue); + break; case JpegColorSpace.YCbCr: - ValidateYCbCr(original, result, i); - break; - default: - Assert.Fail($"Invalid Colorspace enum value: {colorSpace}."); - break; - } - } - - private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128; - float cr = values.Component2[i] - 128; - - float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + c1 -= 128F; + c2 -= 128F; - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + // JPEG applies the BT.601 matrix in the integer sample domain and rounds before normalization. + expected = new Rgb( + MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, + MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, + MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero) / MaxColorChannelValue); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float r = values.Component0[i] / MaxColorChannelValue; - float g = values.Component1[i] / MaxColorChannelValue; - float b = values.Component2[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + break; + case JpegColorSpace.Ycck: + c1 -= 128F; + c2 -= 128F; + c3 = source.Component3[index] / MaxColorChannelValue; - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + // Adobe YccK reconstructs inverted RGB first, then applies the normalized black component. + expected = new Rgb( + (MaxColorChannelValue - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, + (MaxColorChannelValue - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, + (MaxColorChannelValue - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue); - private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(y, y, y)); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + return; + } - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component0[i], result.Component0[i])); + // Color-space comparison intentionally clamps both sides because JPEG reconstruction can overshoot + // the normalized RGB gamut and saturation belongs to the eventual pixel conversion. + Rgb clampedExpected = Rgb.Clamp(expected); + Rgb clampedActual = Rgb.Clamp( + new Rgb(actual.Component0[index], actual.Component1[index], actual.Component2[index])); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + Assert.True( + ColorSpaceComparer.Equals(clampedExpected, clampedActual), + $"Colors {clampedExpected} and {clampedActual} are not equal at index {index}."); } - private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + /// + /// Compares two component planes using an absolute floating-point tolerance. + /// + /// The scalar reference values. + /// The adaptive traversal values. + /// The maximum permitted absolute difference. + private static void CompareSequence(Span expected, Span actual, float tolerance) { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / MaxColorChannelValue; - - float r = c * k / MaxColorChannelValue; - float g = m * k / MaxColorChannelValue; - float b = y * k / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + Assert.Equal(expected.Length, actual.Length); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i], tolerance); + } } } From 1c726da0df83789ce0dcce77271d81524a49a1e3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:24:10 +1000 Subject: [PATCH 228/240] Clean up normalized SIMD pipelines --- .../ColorProfileConverterExtensionsIcc.cs | 9 +- .../Common/Helpers/Shuffle/IPad3Shuffle4.cs | 33 +- .../Common/Helpers/Shuffle/IShuffle3.cs | 15 +- .../Common/Helpers/Shuffle/IShuffle4.cs | 219 ++-- .../Common/Helpers/Shuffle/IShuffle4Slice3.cs | 39 +- .../Common/Helpers/SimdUtils.Shuffle.cs | 94 +- .../Common/Helpers/Vector128Utilities.cs | 19 +- .../Common/Helpers/Vector256Utilities.cs | 6 +- .../Common/Helpers/Vector512Utilities.cs | 20 + .../JpegColorConverter.CmykOperator.cs | 90 +- .../JpegColorConverter.GrayScaleOperator.cs | 105 +- .../JpegColorConverter.Operator.cs | 167 +-- .../JpegColorConverter.Packing.cs | 305 +++++ .../JpegColorConverter.RgbOperator.cs | 90 +- .../JpegColorConverter.YCbCrOperator.cs | 150 +-- .../JpegColorConverter.YccKOperator.cs | 4 + .../ColorConverters/JpegColorConverterBase.cs | 135 +-- .../Components/Encoder/ComponentProcessor.cs | 4 + .../Formats/Png/Filters/AverageFilter.cs | 7 +- .../Formats/Png/Filters/IPngFilterOperator.cs | 341 ++---- .../Formats/Png/Filters/PaethFilter.cs | 7 +- .../Formats/Png/Filters/PngFilterEncoder.cs | 121 +- .../Formats/Png/Filters/SubFilter.cs | 12 +- .../Formats/Png/Filters/UpFilter.cs | 10 +- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 1 + .../Formats/Webp/Lossless/Vp8LHistogram.cs | 18 +- .../AssociatedAlphaPixelBlenders.Generated.cs | 1080 ++++------------- .../AssociatedAlphaPixelBlenders.Generated.tt | 10 +- ...atedAlphaPixelBlender{TPixel,TOperator}.cs | 5 +- .../DefaultPixelBlenders.Generated.cs | 1080 ++++------------- .../DefaultPixelBlenders.Generated.tt | 10 +- .../PixelBlenders/IPixelBlenderOperator.cs | 10 +- .../PixelBlender{TPixel,TOperator}.cs | 302 ++--- .../Utils/Vector4Converters.Affine.cs | 9 +- .../Vector4Converters.AffineOperators.cs | 8 +- .../ColorConversion/CmykColorConversion.cs | 3 +- .../GrayscaleColorConversion.cs | 3 +- .../Jpeg/ColorConversion/JpegColorPacking.cs | 178 +++ .../ColorConversion/JpegColorPackingScalar.cs | 117 ++ .../ColorConversion/RgbColorConversion.cs | 3 +- .../ColorConversion/YCbCrColorConversion.cs | 3 +- .../ColorConversion/YCbCrOperatorAssembly.cs | 244 ---- .../ColorConversion/YccKColorConverter.cs | 3 +- .../Codecs/Png/PngFilterEncodeAssembly.cs | 64 - .../BasicMath/TensorPrimitivesAssembly.cs | 175 --- .../PackedPixelConversionAssembly.cs | 128 -- .../Vector4AffineTransformAssembly.cs | 59 - .../PixelBlenderTraversalAssembly.cs | 327 ----- .../Common/SimdUtilsTests.Shuffle.cs | 70 +- .../Formats/Jpg/JpegColorConverterTests.cs | 105 +- .../Formats/Jpg/JpegColorPackingTests.cs | 168 +++ .../Formats/Png/PngEncoderFilterTests.cs | 79 +- .../PixelFormats/PixelBlenderTests.cs | 16 +- .../PixelFormats/Vector4ConvertersTests.cs | 32 +- 54 files changed, 1839 insertions(+), 4473 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Packing.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs delete mode 100644 tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index 78f88932f..7f08a7a9b 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -660,6 +660,8 @@ internal static class ColorProfileConverterExtensionsIcc private static void ClipNegative(Span source) { + // Vector4 values are contiguous floats, so flattening preserves the component order + // while allowing one shared tensor traversal to process every channel and SIMD tail. Span values = MemoryMarshal.Cast(source); TensorPrimitives_.Max(values, 0F, values); } @@ -680,10 +682,9 @@ internal static class ColorProfileConverterExtensionsIcc private static void LabToLab(Span source, Span destination, [ConstantExpected] float scale) { - TensorPrimitives_.Multiply( - MemoryMarshal.Cast(source), - scale, - MemoryMarshal.Cast(destination)); + // Reinterpreting both spans exposes all four components to one multiplication traversal; + // the source and destination retain their original Vector4 boundaries after the operation. + TensorPrimitives_.Multiply(MemoryMarshal.Cast(source), scale, MemoryMarshal.Cast(destination)); } private class ConversionParams diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs index d9d8e35fc..2c80d8f57 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using SixLabors.ImageSharp.Common.Helpers; @@ -37,11 +35,18 @@ internal readonly struct WXYZPad3Shuffle4 : IPad3Shuffle4 { /// [MethodImpl(InliningOptions.ShortMethod)] - public static uint Invoke(uint source) => BitOperations.RotateLeft(source, 8); + public static uint Invoke(uint source) + + // The scalar pipeline has already appended opaque W, so the four-component + // WXYZ operator performs the complete remaining permutation. + => WXYZShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 0, 1, 2] + // produces WXYZ, and offsets 4, 8, and 12 repeat that rotation for the next pixels. => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14)); } @@ -52,11 +57,18 @@ internal readonly struct WZYXPad3Shuffle4 : IPad3Shuffle4 { /// [MethodImpl(InliningOptions.ShortMethod)] - public static uint Invoke(uint source) => BinaryPrimitives.ReverseEndianness(source); + public static uint Invoke(uint source) + + // The scalar pipeline has already appended opaque W, so the four-component + // WZYX operator performs the complete remaining permutation. + => WZYXShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 2, 1, 0] + // produces WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels. => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); } @@ -68,15 +80,16 @@ internal readonly struct ZYXWPad3Shuffle4 : IPad3Shuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Preserve opaque W and Y while exchanging X and Z. - uint wy = source & 0xFF00FF00; - uint xz = source & 0x00FF00FF; - return wy | BitOperations.RotateLeft(xz, 16); - } + + // The scalar pipeline has already appended opaque W, so the four-component + // ZYXW operator performs the complete remaining permutation. + => ZYXWShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel with opaque W. Selecting [2, 1, 0, 3] + // exchanges X and Z to produce ZYXW, with offsets 4, 8, and 12 covering the next pixels. => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs index eab5a41da..7de8af4e4 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs @@ -22,16 +22,17 @@ internal readonly struct ZYXShuffle3 : IShuffle3 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Y is already centered; shift X and Z directly into each other's byte positions. - uint y = source & 0x0000FF00; - uint x = (source & 0x000000FF) << 16; - uint z = (source & 0x00FF0000) >> 16; - return x | y | z; - } + + // The scalar tail is staged as XYZW with an unused W byte. Reusing the four-component + // ZYXW operator produces ZYX in the low three bytes consumed by the caller. + => ZYXWShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is a temporary XYZW pixel created by the shuffle pipeline. + // Selecting [2, 1, 0, 3] produces ZYXW, and offsets 4, 8, and 12 repeat that + // permutation for the next pixels. The pipeline subsequently discards every W byte. => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs index 2a5a6668f..713de342f 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs @@ -27,6 +27,30 @@ internal interface IShuffle4 : IComponentShuffle /// The source pixels. /// The reordered pixels. public static abstract Vector512 Invoke(Vector512 source); + + /// + /// Expands one 128-bit lane mask into absolute indices for a 512-bit shuffle. + /// + /// The indices, from zero through fifteen, for one 128-bit lane. + /// The corresponding absolute indices for all four 128-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExpandLaneMask(Vector128 laneMask) + { + // A 512-bit vector contains four 128-bit lanes, and each lane contains four packed + // XYZW pixels. The supplied mask addresses bytes 0..15 in the first lane. The managed + // Vector512.Shuffle fallback addresses the complete 64-byte vector, so the same + // permutation must address bytes 16..31, 32..47, and 48..63 in the remaining lanes. + // + // AVX-512BW VPSHUFB instead interprets indices independently within each 128-bit lane + // and uses only the low four bits to select a byte. Adding the lane offsets therefore + // satisfies the managed absolute-index contract without changing the native lane-local + // permutation. + Vector128 lane1 = laneMask + Vector128.Create((byte)16); + Vector128 lane2 = laneMask + Vector128.Create((byte)32); + Vector128 lane3 = laneMask + Vector128.Create((byte)48); + + return Vector512.Create(Vector256.Create(laneMask, lane1), Vector256.Create(lane2, lane3)); + } } /// @@ -37,58 +61,42 @@ internal readonly struct WXYZShuffle4 : IShuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { + // source = [W Z Y X] // ROTL(8, source) = [Z Y X W] - return BitOperations.RotateLeft(source, 8); - } + => BitOperations.RotateLeft(source, 8); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) - => Vector128_.ShuffleNative(source, CreateMask()); + => Vector128_.ShuffleNative(source, CreateLaneMask()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Invoke(Vector256 source) { // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. - Vector128 mask = CreateMask(); + Vector128 mask = CreateLaneMask(); return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Invoke(Vector512 source) - => Vector512_.ShuffleNative(source, CreateMask512()); + + // Expand the four-pixel lane permutation across all four 128-bit lanes. + => Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask())); /// /// Creates the indices that rotate each XYZW pixel to WXYZ within one 128-bit lane. /// /// The pixel-local byte shuffle indices. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 CreateMask() - => Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14); + private static Vector128 CreateLaneMask() - /// - /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. - /// - /// The absolute byte shuffle indices. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 CreateMask512() - { - // Native vpshufb ignores the lane offsets, while Vector512.Shuffle treats the indices as - // absolute. Encoding both meanings keeps the AVX-512 and managed fallback results identical. - return Vector512.Create( - 0x0605040702010003UL, - 0x0E0D0C0F0A09080BUL, - 0x1615141712111013UL, - 0x1E1D1C1F1A19181BUL, - 0x2625242722212023UL, - 0x2E2D2C2F2A29282BUL, - 0x3635343732313033UL, - 0x3E3D3C3F3A39383BUL).AsByte(); - } + // Each four-byte group is one XYZW pixel. Selecting [3, 0, 1, 2] produces + // WXYZ, and offsets 4, 8, and 12 repeat that permutation for the next pixels. + => Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14); } /// @@ -99,52 +107,42 @@ internal readonly struct WZYXShuffle4 : IShuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Reversing the integer's endianness also reverses the four byte components. - return BinaryPrimitives.ReverseEndianness(source); - } + + // source = [W Z Y X] + // REVERSE(source) = [X Y Z W] + => BinaryPrimitives.ReverseEndianness(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) - => Vector128_.ShuffleNative(source, CreateMask()); + => Vector128_.ShuffleNative(source, CreateLaneMask()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Invoke(Vector256 source) { - Vector128 mask = CreateMask(); + // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. + Vector128 mask = CreateLaneMask(); return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Invoke(Vector512 source) - => Vector512_.ShuffleNative(source, CreateMask512()); + + // Expand the four-pixel lane permutation across all four 128-bit lanes. + => Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask())); /// /// Creates the indices that reverse each XYZW pixel to WZYX within one 128-bit lane. /// /// The pixel-local byte shuffle indices. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 CreateMask() - => Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12); + private static Vector128 CreateLaneMask() - /// - /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. - /// - /// The absolute byte shuffle indices. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 CreateMask512() - => Vector512.Create( - 0x0405060700010203UL, - 0x0C0D0E0F08090A0BUL, - 0x1415161710111213UL, - 0x1C1D1E1F18191A1BUL, - 0x2425262720212223UL, - 0x2C2D2E2F28292A2BUL, - 0x3435363730313233UL, - 0x3C3D3E3F38393A3BUL).AsByte(); + // Each four-byte group is one XYZW pixel. Selecting [3, 2, 1, 0] produces + // WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels. + => Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12); } /// @@ -155,53 +153,42 @@ internal readonly struct YZWXShuffle4 : IShuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { + // source = [W Z Y X] // ROTR(8, source) = [X W Z Y] - return BitOperations.RotateRight(source, 8); - } + => BitOperations.RotateRight(source, 8); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) - => Vector128_.ShuffleNative(source, CreateMask()); + => Vector128_.ShuffleNative(source, CreateLaneMask()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Invoke(Vector256 source) { - Vector128 mask = CreateMask(); + // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. + Vector128 mask = CreateLaneMask(); return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Invoke(Vector512 source) - => Vector512_.ShuffleNative(source, CreateMask512()); + + // Expand the four-pixel lane permutation across all four 128-bit lanes. + => Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask())); /// /// Creates the indices that rotate each XYZW pixel to YZWX within one 128-bit lane. /// /// The pixel-local byte shuffle indices. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 CreateMask() - => Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12); + private static Vector128 CreateLaneMask() - /// - /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. - /// - /// The absolute byte shuffle indices. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 CreateMask512() - => Vector512.Create( - 0x0407060500030201UL, - 0x0C0F0E0D080B0A09UL, - 0x1417161510131211UL, - 0x1C1F1E1D181B1A19UL, - 0x2427262520232221UL, - 0x2C2F2E2D282B2A29UL, - 0x3437363530333231UL, - 0x3C3F3E3D383B3A39UL).AsByte(); + // Each four-byte group is one XYZW pixel. Selecting [1, 2, 3, 0] produces + // YZWX, and offsets 4, 8, and 12 repeat that rotation for the next pixels. + => Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12); } /// @@ -212,54 +199,44 @@ internal readonly struct ZYXWShuffle4 : IShuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Preserve W and Y while rotating the masked X/Z bytes into each other's positions. - uint wy = source & 0xFF00FF00; - uint xz = source & 0x00FF00FF; - return wy | BitOperations.RotateLeft(xz, 16); - } + + // source = [W Z Y X] + // source & 0xFF00FF00 = [W 0 Y 0] + // ROTL(source & 0x00FF00FF) = [0 X 0 Z] + // combined = [W X Y Z] + => (source & 0xFF00FF00) | BitOperations.RotateLeft(source & 0x00FF00FF, 16); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) - => Vector128_.ShuffleNative(source, CreateMask()); + => Vector128_.ShuffleNative(source, CreateLaneMask()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Invoke(Vector256 source) { - Vector128 mask = CreateMask(); + // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. + Vector128 mask = CreateLaneMask(); return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Invoke(Vector512 source) - => Vector512_.ShuffleNative(source, CreateMask512()); + + // Expand the four-pixel lane permutation across all four 128-bit lanes. + => Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask())); /// /// Creates the indices that exchange X and Z in each XYZW pixel within one 128-bit lane. /// /// The pixel-local byte shuffle indices. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 CreateMask() - => Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); + private static Vector128 CreateLaneMask() - /// - /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. - /// - /// The absolute byte shuffle indices. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 CreateMask512() - => Vector512.Create( - 0x0704050603000102UL, - 0x0F0C0D0E0B08090AUL, - 0x1714151613101112UL, - 0x1F1C1D1E1B18191AUL, - 0x2724252623202122UL, - 0x2F2C2D2E2B28292AUL, - 0x3734353633303132UL, - 0x3F3C3D3E3B38393AUL).AsByte(); + // Each four-byte group is one XYZW pixel. Selecting [2, 1, 0, 3] exchanges + // X and Z to produce ZYXW, with offsets 4, 8, and 12 covering the next pixels. + => Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); } /// @@ -270,52 +247,42 @@ internal readonly struct XWZYShuffle4 : IShuffle4 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Preserve X and Z while rotating the masked Y/W bytes into each other's positions. - uint xz = source & 0x00FF00FF; - uint yw = source & 0xFF00FF00; - return xz | BitOperations.RotateLeft(yw, 16); - } + + // source = [W Z Y X] + // source & 0x00FF00FF = [0 Z 0 X] + // ROTL(source & 0xFF00FF00) = [Y 0 W 0] + // combined = [Y Z W X] + => (source & 0x00FF00FF) | BitOperations.RotateLeft(source & 0xFF00FF00, 16); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) - => Vector128_.ShuffleNative(source, CreateMask()); + => Vector128_.ShuffleNative(source, CreateLaneMask()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Invoke(Vector256 source) { - Vector128 mask = CreateMask(); + // AVX2 byte shuffles select within 128-bit lanes, so both halves use the same pixel-local indices. + Vector128 mask = CreateLaneMask(); return Vector256_.ShufflePerLane(source, Vector256.Create(mask, mask)); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Invoke(Vector512 source) - => Vector512_.ShuffleNative(source, CreateMask512()); + + // Expand the four-pixel lane permutation across all four 128-bit lanes. + => Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask())); /// /// Creates the indices that exchange Y and W in each XYZW pixel within one 128-bit lane. /// /// The pixel-local byte shuffle indices. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 CreateMask() - => Vector128.Create((byte)0, 3, 2, 1, 4, 7, 6, 5, 8, 11, 10, 9, 12, 15, 14, 13); + private static Vector128 CreateLaneMask() - /// - /// Creates absolute indices for all four 128-bit lanes in a 512-bit vector. - /// - /// The absolute byte shuffle indices. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 CreateMask512() - => Vector512.Create( - 0x0506070401020300UL, - 0x0D0E0F0C090A0B08UL, - 0x1516171411121310UL, - 0x1D1E1F1C191A1B18UL, - 0x2526272421222320UL, - 0x2D2E2F2C292A2B28UL, - 0x3536373431323330UL, - 0x3D3E3F3C393A3B38UL).AsByte(); + // Each four-byte group is one XYZW pixel. Selecting [0, 3, 2, 1] exchanges + // Y and W to produce XWZY, with offsets 4, 8, and 12 covering the next pixels. + => Vector128.Create((byte)0, 3, 2, 1, 4, 7, 6, 5, 8, 11, 10, 9, 12, 15, 14, 13); } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index 17ec503e5..8f32a5a6c 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -38,11 +36,19 @@ internal readonly struct YZWXShuffle4Slice3 : IShuffle4Slice3 { /// [MethodImpl(InliningOptions.ShortMethod)] - public static uint Invoke(uint source) => BitOperations.RotateRight(source, 8); + public static uint Invoke(uint source) + + // Reuse the four-component rotation; the caller stores only the low YZW + // bytes and therefore discards the rotated X byte. + => YZWXShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel. Selecting [1, 2, 3, 0] produces + // YZWX, and offsets 4, 8, and 12 repeat that rotation for the next pixels. + // The surrounding pipeline subsequently removes every fourth byte. => Vector128_.ShuffleNative(source, Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12)); } @@ -53,11 +59,19 @@ internal readonly struct WZYXShuffle4Slice3 : IShuffle4Slice3 { /// [MethodImpl(InliningOptions.ShortMethod)] - public static uint Invoke(uint source) => BinaryPrimitives.ReverseEndianness(source); + public static uint Invoke(uint source) + + // Reuse the four-component reversal; the caller stores only the low WZY + // bytes and therefore discards the reversed X byte. + => WZYXShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel. Selecting [3, 2, 1, 0] produces + // WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels. + // The surrounding pipeline subsequently removes every fourth byte. => Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); } @@ -69,19 +83,24 @@ internal readonly struct ZYXWShuffle4Slice3 : IShuffle4Slice3 /// [MethodImpl(InliningOptions.ShortMethod)] public static uint Invoke(uint source) - { - // Preserve W and Y while exchanging X and Z; W is subsequently discarded. - uint wy = source & 0xFF00FF00; - uint xz = source & 0x00FF00FF; - return wy | BitOperations.RotateLeft(xz, 16); - } + + // Reuse the four-component exchange; the caller stores only the low ZYX + // bytes and therefore discards the preserved W byte. + => ZYXWShuffle4.Invoke(source); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Invoke(Vector128 source) + + // Each four-byte group is an XYZW pixel. Selecting [2, 1, 0, 3] produces + // ZYXW, and offsets 4, 8, and 12 repeat that exchange for the next pixels. + // The surrounding pipeline subsequently removes every fourth byte. => Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); } +/// +/// Represents one tightly packed three-byte value for scalar four-to-three component writes. +/// [StructLayout(LayoutKind.Explicit, Size = 3)] internal readonly struct Byte3 { diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index e709cc8d4..0eeb4832f 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -67,22 +67,17 @@ internal static partial class SimdUtils { // Four independent vectors amortize loop control and expose enough work for the CPU // to overlap loads, byte shuffles, and stores without changing pixel ordering. - TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); - TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))) - .StoreUnsafe(ref destinationBase, (nuint)(i + Vector512.Count)); - TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 2)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 2))); - TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 3)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 3))); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))).StoreUnsafe(ref destinationBase, (nuint)(i + Vector512.Count)); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 2)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 2))); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector512.Count * 3)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector512.Count * 3))); } int oneVectorFromEnd = length - Vector512.Count; for (; i <= oneVectorFromEnd; i += Vector512.Count) { - TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); } } @@ -92,22 +87,17 @@ internal static partial class SimdUtils for (; i <= fourVectorsFromEnd; i += Vector256.Count * 4) { - TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); - TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))) - .StoreUnsafe(ref destinationBase, (nuint)(i + Vector256.Count)); - TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 2)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 2))); - TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 3)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 3))); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))).StoreUnsafe(ref destinationBase, (nuint)(i + Vector256.Count)); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 2)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 2))); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector256.Count * 3)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector256.Count * 3))); } int oneVectorFromEnd = length - Vector256.Count; for (; i <= oneVectorFromEnd; i += Vector256.Count) { - TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); } } @@ -117,22 +107,17 @@ internal static partial class SimdUtils for (; i <= fourVectorsFromEnd; i += Vector128.Count * 4) { - TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); - TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))) - .StoreUnsafe(ref destinationBase, (nuint)(i + Vector128.Count)); - TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 2)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 2))); - TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 3)))) - .StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 3))); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))).StoreUnsafe(ref destinationBase, (nuint)(i + Vector128.Count)); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 2)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 2))); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + (Vector128.Count * 3)))).StoreUnsafe(ref destinationBase, (nuint)(i + (Vector128.Count * 3))); } int oneVectorFromEnd = length - Vector128.Count; for (; i <= oneVectorFromEnd; i += Vector128.Count) { - TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)) - .StoreUnsafe(ref destinationBase, (nuint)i); + TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)).StoreUnsafe(ref destinationBase, (nuint)i); } } @@ -167,15 +152,17 @@ internal static partial class SimdUtils if (Vector128.IsHardwareAccelerated) { - // Each group contains sixteen XYZ pixels in three registers. The pad mask expands - // four triplets per register to XYZW, with 0x80 selecting zero for the temporary W lane. - Vector128 padMask = Vector128.Create( - (byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); - - // After the operator has reordered padded pixels, these masks remove every temporary - // W lane and repack the four registers into three contiguous XYZ destination registers. - Vector128 sliceMask = Vector128.Create( - (byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); + // Each group contains sixteen XYZ pixels in three registers. For a register beginning + // [X0,Y0,Z0,X1,Y1,Z1,...], the indices [0,1,2,0x80,3,4,5,0x80,...] + // produce four [X,Y,Z,0] pixels. Index 0x80 selects zero on the native byte-shuffle + // instructions and on the portable helper, creating a temporary W lane for TShuffle. + Vector128 padMask = Vector128.Create((byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); + + // After TShuffle places the retained components in bytes 0..2 of each four-byte pixel, + // [0,1,2,4,5,6,8,9,10,12,13,14] packs four triplets into twelve bytes. Rotating that + // mask by twelve positions moves the packed bytes four positions right. Alternating the + // two alignments lets AlignRight stitch four twelve-byte results into three full registers. + Vector128 sliceMask = Vector128.Create((byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); Vector128 sliceEndMask = Vector128_.AlignRight(sliceMask, sliceMask, 12); ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); @@ -284,10 +271,13 @@ internal static partial class SimdUtils if (Vector128.IsHardwareAccelerated) { - // The fixed mask expands four XYZ triplets to four XYZW pixels. The zeroed W bytes - // are then filled with opaque alpha before the selected operator reorders each pixel. - Vector128 padMask = Vector128.Create( - (byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); + // For source bytes [X0,Y0,Z0,X1,Y1,Z1,...], the indices + // [0,1,2,0x80,3,4,5,0x80,...] form four [X,Y,Z,0] pixels. The native + // and portable shuffle paths both interpret 0x80 as a zero-producing index. + Vector128 padMask = Vector128.Create((byte)0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80); + + // The broadcast ulong has 0xFF in bytes 3 and 7; repeating it across 128 bits + // fills W at byte positions 3, 7, 11, and 15 without modifying X, Y, or Z. Vector128 opaqueAlpha = Vector128.Create(0xFF000000FF000000UL).AsByte(); ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); @@ -366,10 +356,14 @@ internal static partial class SimdUtils if (Vector128.IsHardwareAccelerated) { - // Each operator first places the three retained components in the low bytes of every - // four-byte pixel. These masks then delete the fourth byte and compact sixteen pixels. - Vector128 sliceMask = Vector128.Create( - (byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); + // Each operator first places the retained components in bytes 0..2 of every four-byte + // pixel. The indices [0,1,2,4,5,6,8,9,10,12,13,14] delete each fourth byte and + // pack four triplets into the low twelve bytes. Indices 0x80 zero the unused bytes. + Vector128 sliceMask = Vector128.Create((byte)0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80); + + // Rotating the mask by twelve moves its packed result four bytes right. Alternating + // shifted and unshifted results gives AlignRight the overlap needed to concatenate + // four twelve-byte groups into three complete destination registers. Vector128 sliceEndMask = Vector128_.AlignRight(sliceMask, sliceMask, 12); ref Vector128 sourceVectors = ref Unsafe.As>(ref sourceBase); ref Vector128 destinationVectors = ref Unsafe.As>(ref destinationBase); @@ -377,8 +371,7 @@ internal static partial class SimdUtils nuint sourceVectorIndex = 0; nuint destinationVectorIndex = 0; - for (; sourceVectorIndex + 3 < sourceVectorCount; - sourceVectorIndex += 4, destinationVectorIndex += 3) + for (; sourceVectorIndex + 3 < sourceVectorCount; sourceVectorIndex += 4, destinationVectorIndex += 3) { // Load and transform all sixteen source pixels before writing the shorter output group. // This preserves forward progress when source and destination begin at the same address. @@ -413,8 +406,7 @@ internal static partial class SimdUtils { // The operator arranges the three retained components at the front of each pixel. // One fixed shuffle then compacts four pixels into the low twelve vector bytes. - Vector128 result = TShuffle.Invoke( - Vector128.LoadUnsafe(ref sourceBase, (nuint)sourceOffset)); + Vector128 result = TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)sourceOffset)); result = Vector128_.ShuffleNative(result, sliceMask); diff --git a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs index ce8af4d6f..4a53fdda4 100644 --- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs @@ -1296,22 +1296,9 @@ internal static class Vector128_ return PackedSimd.SubtractSaturate(left, right); } - // Widen inputs to 16-bit - (Vector128 leftLo, Vector128 leftHi) = Vector128.Widen(left); - (Vector128 rightLo, Vector128 rightHi) = Vector128.Widen(right); - - // Subtract - Vector128 diffLo = leftLo - rightLo; - Vector128 diffHi = leftHi - rightHi; - - // Clamp to signed 8-bit range - Vector128 max = Vector128.Create((ushort)byte.MaxValue); - - diffLo = Clamp(diffLo, Vector128.Zero, max); - diffHi = Clamp(diffHi, Vector128.Zero, max); - - // Narrow back to bytes - return Vector128.Narrow(diffLo, diffHi); + // Subtracting the smaller operand implements the .NET 10 unsigned contract: + // lanes where right exceeds left subtract left from itself and therefore saturate at zero. + return left - Vector128.Min(left, right); } /// diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs index 1f3ebb35f..681f80013 100644 --- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs @@ -495,9 +495,9 @@ internal static class Vector256_ return Avx2.SubtractSaturate(left, right); } - return Vector256.Create( - Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()), - Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper())); + // The .NET 10 portable implementation applies the same saturated operation to + // both 128-bit halves, allowing each half to select its native instruction set. + return Vector256.Create(Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()), Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper())); } /// diff --git a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs index 0193a51ba..d2c5b4a87 100644 --- a/src/ImageSharp/Common/Helpers/Vector512Utilities.cs +++ b/src/ImageSharp/Common/Helpers/Vector512Utilities.cs @@ -129,6 +129,26 @@ internal static class Vector512_ return Vector512.Create(lower, upper); } + /// + /// Subtracts packed unsigned 8-bit integers in from + /// , saturating negative lane results to zero. + /// + /// The vector from which is subtracted. + /// The vector to subtract from . + /// The element-wise saturated differences. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSaturate(Vector512 left, Vector512 right) + { + if (Avx512BW.IsSupported) + { + return Avx512BW.SubtractSaturate(left, right); + } + + // This mirrors the .NET 10 portable implementation: recursively processing both + // 256-bit halves preserves lane order and lets each half select its available ISA. + return Vector512.Create(Vector256_.SubtractSaturate(left.GetLower(), right.GetLower()), Vector256_.SubtractSaturate(left.GetUpper(), right.GetUpper())); + } + /// /// Performs a multiplication and a negated addition of the . /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs index dad093124..2f67e0f53 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs @@ -27,14 +27,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref float c0, - ref float c1, - ref float c2, - float c3, - float maximumValue, - float halfValue, - float scale) + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) { // Adobe-style CMYK stores inverted component samples. Multiplying K by scale twice folds the // two sample-domain divisions into one factor before it modulates the C, M, and Y planes. @@ -46,14 +39,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector128 c0, - ref Vector128 c1, - ref Vector128 c2, - Vector128 c3, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale) + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) { // Each K lane supplies the common modulation factor for the corresponding C, M, and Y lanes. Vector128 scaledK = c3 * scale * scale; @@ -64,14 +50,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector256 c0, - ref Vector256 c1, - ref Vector256 c2, - Vector256 c3, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale) + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) { // Eight independent CMYK samples remain lane-aligned throughout the modulation. Vector256 scaledK = c3 * scale * scale; @@ -82,14 +61,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector512 c0, - ref Vector512 c1, - ref Vector512 c2, - Vector512 c3, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale) + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) { // Sixteen independent CMYK samples remain lane-aligned throughout the modulation. Vector512 scaledK = c3 * scale * scale; @@ -100,17 +72,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - float r, - float g, - float b, - float maximumValue, - float halfValue, - float scale, - out float c0, - out float c1, - out float c2, - out float c3) + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) { float c = maximumValue - r; float m = maximumValue - g; @@ -144,17 +106,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector128 r, - Vector128 g, - Vector128 b, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3) + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) { Vector128 c = maximumValue - r; Vector128 m = maximumValue - g; @@ -176,17 +128,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector256 r, - Vector256 g, - Vector256 b, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3) + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) { Vector256 c = maximumValue - r; Vector256 m = maximumValue - g; @@ -208,17 +150,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector512 r, - Vector512 g, - Vector512 b, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3) + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) { Vector512 c = maximumValue - r; Vector512 m = maximumValue - g; @@ -240,11 +172,7 @@ internal abstract partial class JpegColorConverterBase } /// - public static void ConvertToRgbInPlaceWithIcc( - Configuration configuration, - IccProfile profile, - in ComponentValues values, - float maximumValue) + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) { using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); Span packed = memoryOwner.Memory.Span; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs index 20b68e8b1..8522fbf5c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs @@ -28,14 +28,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref float c0, - ref float c1, - ref float c2, - float c3, - float maximumValue, - float halfValue, - float scale) + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) { // JPEG stores luminance in the integer sample domain. Normalize it once, then duplicate the // same value into all three RGB planes. Keeping it local also prevents potentially aliasing @@ -48,14 +41,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector128 c0, - ref Vector128 c1, - ref Vector128 c2, - Vector128 c3, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale) + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) { // Each XMM lane is one independent luminance sample. Reusing the normalized vector for R, G, // and B avoids recomputing the scale and keeps it live across potentially aliasing byref stores. @@ -67,14 +53,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector256 c0, - ref Vector256 c1, - ref Vector256 c2, - Vector256 c3, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale) + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) { // Eight luminance samples occupy the YMM lanes. The local retains the normalized vector across // all three output stores even when the destination planes alias. @@ -86,14 +65,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector512 c0, - ref Vector512 c1, - ref Vector512 c2, - Vector512 c3, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale) + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) { // Sixteen luminance samples occupy the ZMM lanes. The local retains the normalized vector across // all three output stores without shuffles, interleaving, or source reloads. @@ -105,17 +77,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - float r, - float g, - float b, - float maximumValue, - float halfValue, - float scale, - out float c0, - out float c1, - out float c2, - out float c3) + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) { // Rec.601 luma weights operate directly in the encoder sample domain. Only c0 is stored for a // one-component model; the remaining out values exist solely to satisfy the common operator shape. @@ -127,23 +89,10 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector128 r, - Vector128 g, - Vector128 b, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3) + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) { // The nested estimate gives each pixel the same multiply-add grouping as the scalar Rec.601 formula. - c0 = Vector128_.MultiplyAddEstimate( - Vector128.Create(0.299F), - r, - Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); + c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); c1 = default; c2 = default; c3 = default; @@ -151,23 +100,10 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector256 r, - Vector256 g, - Vector256 b, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3) + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) { // YMM lanes evaluate the same Rec.601 equation independently, with no horizontal lane reduction. - c0 = Vector256_.MultiplyAddEstimate( - Vector256.Create(0.299F), - r, - Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); + c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); c1 = default; c2 = default; c3 = default; @@ -175,34 +111,17 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector512 r, - Vector512 g, - Vector512 b, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3) + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) { // ZMM lanes retain the same arithmetic order as narrower paths so only SIMD width changes. - c0 = Vector512_.MultiplyAddEstimate( - Vector512.Create(0.299F), - r, - Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); + c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); c1 = default; c2 = default; c3 = default; } /// - public static void ConvertToRgbInPlaceWithIcc( - Configuration configuration, - IccProfile profile, - in ComponentValues values, - float maximumValue) + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) { using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); Span packed = memoryOwner.Memory.Span; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs index 86851a9d6..0058a6213 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs @@ -40,14 +40,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of . - public static abstract void ConvertToRgb( - ref float c0, - ref float c1, - ref float c2, - float c3, - float maximumValue, - float halfValue, - float scale); + public static abstract void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale); /// /// Converts four JPEG samples to normalized RGB. @@ -59,14 +52,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - public static abstract void ConvertToRgb( - ref Vector128 c0, - ref Vector128 c1, - ref Vector128 c2, - Vector128 c3, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale); + public static abstract void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale); /// /// Converts eight JPEG samples to normalized RGB. @@ -78,14 +64,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - public static abstract void ConvertToRgb( - ref Vector256 c0, - ref Vector256 c1, - ref Vector256 c2, - Vector256 c3, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale); + public static abstract void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale); /// /// Converts sixteen JPEG samples to normalized RGB. @@ -97,14 +76,7 @@ internal abstract partial class JpegColorConverterBase /// The maximum component value for the configured precision. /// The midpoint component value for the configured precision. /// The reciprocal of in every lane. - public static abstract void ConvertToRgb( - ref Vector512 c0, - ref Vector512 c1, - ref Vector512 c2, - Vector512 c3, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale); + public static abstract void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale); /// /// Converts one RGB sample to JPEG components. @@ -119,17 +91,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component. /// The third converted component. /// The fourth converted component, if used. - public static abstract void ConvertFromRgb( - float r, - float g, - float b, - float maximumValue, - float halfValue, - float scale, - out float c0, - out float c1, - out float c2, - out float c3); + public static abstract void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3); /// /// Converts four RGB samples to JPEG components. @@ -144,17 +106,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - public static abstract void ConvertFromRgb( - Vector128 r, - Vector128 g, - Vector128 b, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3); + public static abstract void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3); /// /// Converts eight RGB samples to JPEG components. @@ -169,17 +121,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - public static abstract void ConvertFromRgb( - Vector256 r, - Vector256 g, - Vector256 b, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3); + public static abstract void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3); /// /// Converts sixteen RGB samples to JPEG components. @@ -194,17 +136,7 @@ internal abstract partial class JpegColorConverterBase /// The second converted component lanes. /// The third converted component lanes. /// The fourth converted component lanes, if used. - public static abstract void ConvertFromRgb( - Vector512 r, - Vector512 g, - Vector512 b, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3); + public static abstract void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3); /// /// Converts JPEG component values to RGB using the supplied ICC profile. @@ -213,11 +145,7 @@ internal abstract partial class JpegColorConverterBase /// The source ICC profile. /// The component values to convert. /// The maximum component value for the configured precision. - public static abstract void ConvertToRgbInPlaceWithIcc( - Configuration configuration, - IccProfile profile, - in ComponentValues values, - float maximumValue); + public static abstract void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue); } /// @@ -241,13 +169,7 @@ internal abstract partial class JpegColorConverterBase /// public override int ElementsPerBatch - => Vector512.IsHardwareAccelerated - ? Vector512.Count - : Vector256.IsHardwareAccelerated - ? Vector256.Count - : Vector128.IsHardwareAccelerated - ? Vector128.Count - : 1; + => Vector512.IsHardwareAccelerated ? Vector512.Count : Vector256.IsHardwareAccelerated ? Vector256.Count : Vector128.IsHardwareAccelerated ? Vector128.Count : 1; /// public override void ConvertToRgbInPlace(in ComponentValues values) @@ -289,9 +211,7 @@ internal abstract partial class JpegColorConverterBase // ComponentCount is a static property on the closed operator type, so the JIT removes // this choice. Three-component models never dereference the empty Component3 byref. - Vector512 c3 = TOperator.ComponentCount == 4 - ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) - : default; + Vector512 c3 = TOperator.ComponentCount == 4 ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) : default; // c0-c2 alias the planar source vectors and are replaced in place with normalized RGB. // c3 is passed by value because the fourth JPEG component must remain unchanged. @@ -321,9 +241,7 @@ internal abstract partial class JpegColorConverterBase // The closed operator makes this a compile-time color-model choice, not a per-vector // runtime abstraction or interface dispatch. - Vector256 c3 = TOperator.ComponentCount == 4 - ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) - : default; + Vector256 c3 = TOperator.ComponentCount == 4 ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) : default; TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); } @@ -350,9 +268,7 @@ internal abstract partial class JpegColorConverterBase ref Vector128 c2 = ref Unsafe.As>(ref Unsafe.Add(ref c2Base, i)); // As at the wider stages, the fourth vector is loaded only for CMYK-shaped operators. - Vector128 c3 = TOperator.ComponentCount == 4 - ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) - : default; + Vector128 c3 = TOperator.ComponentCount == 4 ? Unsafe.As>(ref Unsafe.Add(ref c3Base, i)) : default; TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); } @@ -365,14 +281,7 @@ internal abstract partial class JpegColorConverterBase { float c3 = TOperator.ComponentCount == 4 ? Unsafe.Add(ref c3Base, i) : 0; - TOperator.ConvertToRgb( - ref Unsafe.Add(ref c0Base, i), - ref Unsafe.Add(ref c1Base, i), - ref Unsafe.Add(ref c2Base, i), - c3, - this.MaximumValue, - this.HalfValue, - scale); + TOperator.ConvertToRgb(ref Unsafe.Add(ref c0Base, i), ref Unsafe.Add(ref c1Base, i), ref Unsafe.Add(ref c2Base, i), c3, this.MaximumValue, this.HalfValue, scale); } } @@ -420,17 +329,7 @@ internal abstract partial class JpegColorConverterBase Vector512 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); Vector512 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); - TOperator.ConvertFromRgb( - r, - g, - b, - maximumValue, - halfValue, - scaleVector, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3); + TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3); // Outputs remain planar: each vector contains sixteen consecutive samples from one // JPEG component. Static count checks prevent grayscale from touching absent planes @@ -473,17 +372,7 @@ internal abstract partial class JpegColorConverterBase Vector256 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); Vector256 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); - TOperator.ConvertFromRgb( - r, - g, - b, - maximumValue, - halfValue, - scaleVector, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3); + TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3); // Static count checks write only planes owned by this color model. Unsafe.As>(ref Unsafe.Add(ref c0Base, i)) = c0; @@ -524,17 +413,7 @@ internal abstract partial class JpegColorConverterBase Vector128 g = Unsafe.As>(ref Unsafe.Add(ref gBase, i)); Vector128 b = Unsafe.As>(ref Unsafe.Add(ref bBase, i)); - TOperator.ConvertFromRgb( - r, - g, - b, - maximumValue, - halfValue, - scaleVector, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3); + TOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scaleVector, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3); // Four results are stored only for the planes represented by the closed operator. Unsafe.As>(ref Unsafe.Add(ref c0Base, i)) = c0; @@ -560,17 +439,7 @@ internal abstract partial class JpegColorConverterBase // Scalar conversion is reserved for the zero-to-three samples that cannot fill Vector128. for (; i < length; i++) { - TOperator.ConvertFromRgb( - Unsafe.Add(ref rBase, i), - Unsafe.Add(ref gBase, i), - Unsafe.Add(ref bBase, i), - this.MaximumValue, - this.HalfValue, - scale, - out float c0, - out float c1, - out float c2, - out float c3); + TOperator.ConvertFromRgb(Unsafe.Add(ref rBase, i), Unsafe.Add(ref gBase, i), Unsafe.Add(ref bBase, i), this.MaximumValue, this.HalfValue, scale, out float c0, out float c1, out float c2, out float c3); Unsafe.Add(ref c0Base, i) = c0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Packing.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Packing.cs new file mode 100644 index 000000000..749e4773d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Packing.cs @@ -0,0 +1,305 @@ +// 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.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Normalizes three planar component lanes and interleaves them into packed XYZ values. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The destination ordered as consecutive XYZ triples. + /// The normalization factor applied to every component. + public static void PackedNormalizeInterleave3(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, Span packed, float scale) + { + DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); + + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + Vector128 scaleVector = Vector128.Create(scale); + int oneVectorFromEnd = xLane.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // Each source vector contains four consecutive samples from one plane: + // x = [X0 X1 X2 X3] + // y = [Y0 Y1 Y2 Y3] + // z = [Z0 Z1 Z2 Z3] + // Shifting X by one sample supplies the value that follows each XYZ triple: + // shiftedX = [X1 X2 X3 0] + // The transpose therefore produces overlapping rows [Xn Yn Zn Xn+1]. + // AlignRight joins those rows into three complete destination vectors, avoiding + // the scalar-sized stores that writing four independent Vector3 values requires. + Vector128 x = Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; + Vector128 y = Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; + Vector128 z = Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; + Vector128 shiftedX = Vector128_.ShiftRightBytesInVector(x.AsByte(), sizeof(float)).AsSingle(); + + Transpose4(x, y, z, shiftedX, out Vector128 pixel0, out Vector128 pixel1, out Vector128 pixel2, out Vector128 pixel3); + + // Dropping pixel2.X lets [Y2] complete [Y1 Z1 X2] from pixel1. + Vector128 shiftedPixel2 = Vector128_.ShiftRightBytesInVector(pixel2.AsByte(), sizeof(float)); + Vector128 packed1 = Vector128_.AlignRight(shiftedPixel2, pixel1.AsByte(), sizeof(float)).AsSingle(); + + // Dropping pixel3.X leaves [Y3 Z3] to complete [Z2 X3] from pixel2. + Vector128 shiftedPixel3 = Vector128_.ShiftRightBytesInVector(pixel3.AsByte(), sizeof(float)); + Vector128 packed2 = Vector128_.AlignRight(shiftedPixel3, pixel2.AsByte(), sizeof(float) * 2).AsSingle(); + + ref Vector128 destination = ref Unsafe.As>(ref Unsafe.Add(ref packedRef, (uint)i * 3)); + + destination = pixel0; + Unsafe.Add(ref destination, 1) = packed1; + Unsafe.Add(ref destination, 2) = packed2; + } + } + + // Fewer than four pixels remain after SIMD, or every pixel reaches this path + // when the runtime cannot accelerate the cross-vector transpose. + for (; i < xLane.Length; i++) + { + nuint sourceOffset = (uint)i; + nuint packedOffset = sourceOffset * 3; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; + } + } + + /// + /// Deinterleaves packed XYZ values into three planar component lanes. + /// + /// The source ordered as consecutive XYZ triples. + /// The destination X components. + /// The destination Y components. + /// The destination Z components. + public static void UnpackDeinterleave3(ReadOnlySpan packed, Span xLane, Span yLane, Span zLane) + { + DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + + ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + int oneVectorFromEnd = packed.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // A Vector3 occupies twelve contiguous bytes, so a sixteen-byte load beginning + // at one pixel also reads the X component of the following pixel: + // pixel0 = [X0 Y0 Z0 X1] + // pixel1 = [X1 Y1 Z1 X2] + // The transpose discards this fourth column, making the overlap useful padding + // and avoiding two insert instructions per pixel. The final row needs explicit + // zero padding only when pixel3 is the last element in the source span. + nuint packedOffset = (uint)i * 3; + Vector128 pixel0 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset)); + Vector128 pixel1 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset + 3)); + Vector128 pixel2 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset + 6)); + ref float pixel3Ref = ref Unsafe.Add(ref packedRef, packedOffset + 9); + Vector128 pixel3 = i + Vector128.Count < packed.Length ? Unsafe.As>(ref pixel3Ref) : Unsafe.As(ref pixel3Ref).AsVector128(); + + Transpose4(pixel0, pixel1, pixel2, pixel3, out Vector128 x, out Vector128 y, out Vector128 z, out _); + + Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i)) = x; + Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i)) = y; + Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i)) = z; + } + } + + // The scalar remainder preserves the original scatter behavior for zero to + // three pixels and provides the complete fallback on unsupported hardware. + for (; i < packed.Length; i++) + { + nuint packedOffset = (uint)i * 3; + Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); + Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); + Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); + } + } + + /// + /// Normalizes four planar component lanes and interleaves them into packed XYZW values. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The planar W components. + /// The destination ordered as consecutive XYZW groups. + /// The maximum component value used to normalize each component. + public static void PackedNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maxValue) + { + DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); + DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); + + float scale = 1F / maxValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + Vector128 scaleVector = Vector128.Create(scale); + int oneVectorFromEnd = xLane.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // Four planar vectors form the rows of a 4x4 matrix. Transposition + // converts them into four complete [Xn Yn Zn Wn] pixel vectors, so + // normalization and interleaving require only four loads, four + // multiplies, the register transpose, and four contiguous stores. + Vector128 x = Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; + Vector128 y = Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; + Vector128 z = Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; + Vector128 w = Unsafe.As>(ref Unsafe.Add(ref wLaneRef, i)) * scaleVector; + + Transpose4(x, y, z, w, out Vector128 pixel0, out Vector128 pixel1, out Vector128 pixel2, out Vector128 pixel3); + + ref Vector128 destination = ref Unsafe.As>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); + + destination = pixel0; + Unsafe.Add(ref destination, 1) = pixel1; + Unsafe.Add(ref destination, 2) = pixel2; + Unsafe.Add(ref destination, 3) = pixel3; + } + } + + // Process the zero-to-three trailing pixels with the same normalization + // and component order as the vector transpose. + for (; i < xLane.Length; i++) + { + nuint sourceOffset = (uint)i; + nuint packedOffset = sourceOffset * 4; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, sourceOffset) * scale; + } + } + + /// + /// Inverts and normalizes four planar component lanes before interleaving them into packed XYZW values. + /// + /// The inverted planar X components. + /// The inverted planar Y components. + /// The inverted planar Z components. + /// The inverted planar W components. + /// The destination ordered as consecutive conventional XYZW groups. + /// The maximum component value used for inversion and normalization. + public static void PackedInvertNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maxValue) + { + DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); + DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); + + float scale = 1F / maxValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + Vector128 maximumVector = Vector128.Create(maxValue); + Vector128 scaleVector = Vector128.Create(scale); + int oneVectorFromEnd = xLane.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // Adobe JPEG stores all four components inverted in the sample + // domain. Reflecting and normalizing the planar vectors before the + // transpose keeps both arithmetic operations lane-wise and leaves + // the transpose responsible only for the planar-to-packed layout. + Vector128 x = (maximumVector - Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i))) * scaleVector; + Vector128 y = (maximumVector - Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i))) * scaleVector; + Vector128 z = (maximumVector - Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i))) * scaleVector; + Vector128 w = (maximumVector - Unsafe.As>(ref Unsafe.Add(ref wLaneRef, i))) * scaleVector; + + Transpose4(x, y, z, w, out Vector128 pixel0, out Vector128 pixel1, out Vector128 pixel2, out Vector128 pixel3); + + ref Vector128 destination = ref Unsafe.As>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); + + destination = pixel0; + Unsafe.Add(ref destination, 1) = pixel1; + Unsafe.Add(ref destination, 2) = pixel2; + Unsafe.Add(ref destination, 3) = pixel3; + } + } + + // Preserve the original operation order for the zero-to-three trailing pixels: + // subtract in the sample domain, then multiply by the reciprocal maximum. + for (; i < xLane.Length; i++) + { + nuint sourceOffset = (uint)i; + nuint packedOffset = sourceOffset * 4; + Unsafe.Add(ref packedRef, packedOffset) = (maxValue - Unsafe.Add(ref xLaneRef, sourceOffset)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = (maxValue - Unsafe.Add(ref yLaneRef, sourceOffset)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = (maxValue - Unsafe.Add(ref zLaneRef, sourceOffset)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = (maxValue - Unsafe.Add(ref wLaneRef, sourceOffset)) * scale; + } + } + + /// + /// Transposes four four-lane rows into four four-lane columns. + /// + /// The first matrix row. + /// The second matrix row. + /// The third matrix row. + /// The fourth matrix row. + /// The first matrix column. + /// The second matrix column. + /// The third matrix column. + /// The fourth matrix column. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Transpose4(Vector128 row0, Vector128 row1, Vector128 row2, Vector128 row3, out Vector128 column0, out Vector128 column1, out Vector128 column2, out Vector128 column3) + { + // The first unpack interleaves adjacent 32-bit lanes from rows 0/1 and 2/3: + // row01Low = [r0c0 r1c0 r0c1 r1c1] + // row23Low = [r2c0 r3c0 r2c1 r3c1] + // A second unpack treats each adjacent pair as one 64-bit lane and combines + // the row01 and row23 pairs into complete columns. The integer views only + // expose the cross-platform unpack helpers; every floating-point bit is preserved. + Vector128 row01Low = Vector128_.UnpackLow(row0.AsInt32(), row1.AsInt32()); + Vector128 row01High = Vector128_.UnpackHigh(row0.AsInt32(), row1.AsInt32()); + Vector128 row23Low = Vector128_.UnpackLow(row2.AsInt32(), row3.AsInt32()); + Vector128 row23High = Vector128_.UnpackHigh(row2.AsInt32(), row3.AsInt32()); + + column0 = Vector128_.UnpackLow(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); + column1 = Vector128_.UnpackHigh(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); + column2 = Vector128_.UnpackLow(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); + column3 = Vector128_.UnpackHigh(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs index 8559b818d..a97144de9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs @@ -27,14 +27,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref float c0, - ref float c1, - ref float c2, - float c3, - float maximumValue, - float halfValue, - float scale) + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) { // The JPEG planes already represent R, G, and B. Conversion therefore consists only of moving // each integer-domain sample into the normalized floating-point domain consumed by pixel packing. @@ -45,14 +38,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector128 c0, - ref Vector128 c1, - ref Vector128 c2, - Vector128 c3, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale) + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) { // Four samples from each planar channel remain in their lanes while sharing one normalization vector. c0 *= scale; @@ -62,14 +48,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector256 c0, - ref Vector256 c1, - ref Vector256 c2, - Vector256 c3, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale) + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) { // Eight samples per plane are normalized independently without channel shuffles. c0 *= scale; @@ -79,14 +58,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector512 c0, - ref Vector512 c1, - ref Vector512 c2, - Vector512 c3, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale) + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) { // Sixteen samples per plane are normalized independently without changing planar ordering. c0 *= scale; @@ -96,17 +68,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - float r, - float g, - float b, - float maximumValue, - float halfValue, - float scale, - out float c0, - out float c1, - out float c2, - out float c3) + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) { // Encoder RGB lanes already use the JPEG sample domain, so the direct color model copies them. c0 = r; @@ -117,17 +79,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector128 r, - Vector128 g, - Vector128 b, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3) + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) { // The planar vectors map one-to-one to JPEG components; the fourth result is statically discarded. c0 = r; @@ -138,17 +90,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector256 r, - Vector256 g, - Vector256 b, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3) + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) { // The planar vectors map one-to-one to JPEG components; no arithmetic or rearrangement is required. c0 = r; @@ -159,17 +101,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector512 r, - Vector512 g, - Vector512 b, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3) + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) { // The widest path is likewise a register-to-register planar copy for sixteen pixels. c0 = r; @@ -179,11 +111,7 @@ internal abstract partial class JpegColorConverterBase } /// - public static void ConvertToRgbInPlaceWithIcc( - Configuration configuration, - IccProfile profile, - in ComponentValues values, - float maximumValue) + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) { using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); Span packed = memoryOwner.Memory.Span; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs index 931b96dff..9ceb7b44b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs @@ -48,14 +48,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref float c0, - ref float c1, - ref float c2, - float c3, - float maximumValue, - float halfValue, - float scale) + public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) { float y = c0; float cb = c1 - halfValue; @@ -72,14 +65,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector128 c0, - ref Vector128 c1, - ref Vector128 c2, - Vector128 c3, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale) + public static void ConvertToRgb(ref Vector128 c0, ref Vector128 c1, ref Vector128 c2, Vector128 c3, Vector128 maximumValue, Vector128 halfValue, Vector128 scale) { Vector128 y = c0; Vector128 cb = c1 - halfValue; @@ -89,10 +75,7 @@ internal abstract partial class JpegColorConverterBase // R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample // domain before the common normalization scale so all precisions use integer JPEG sample semantics. Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y); - Vector128 g = Vector128_.MultiplyAddEstimate( - cr, - Vector128.Create(-GCrMult), - Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y)); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y)); Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y); c0 = Vector128_.RoundToNearestInteger(r) * scale; @@ -102,14 +85,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector256 c0, - ref Vector256 c1, - ref Vector256 c2, - Vector256 c3, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale) + public static void ConvertToRgb(ref Vector256 c0, ref Vector256 c1, ref Vector256 c2, Vector256 c3, Vector256 maximumValue, Vector256 halfValue, Vector256 scale) { Vector256 y = c0; Vector256 cb = c1 - halfValue; @@ -119,10 +95,7 @@ internal abstract partial class JpegColorConverterBase // Keeping an explicit overload allows the JIT to emit native YMM operations without a width // switch or decomposing the vector into smaller values. Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y); - Vector256 g = Vector256_.MultiplyAddEstimate( - cr, - Vector256.Create(-GCrMult), - Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y)); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y)); Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y); c0 = Vector256_.RoundToNearestInteger(r) * scale; @@ -132,14 +105,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertToRgb( - ref Vector512 c0, - ref Vector512 c1, - ref Vector512 c2, - Vector512 c3, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale) + public static void ConvertToRgb(ref Vector512 c0, ref Vector512 c1, ref Vector512 c2, Vector512 c3, Vector512 maximumValue, Vector512 halfValue, Vector512 scale) { Vector512 y = c0; Vector512 cb = c1 - halfValue; @@ -149,10 +115,7 @@ internal abstract partial class JpegColorConverterBase // assembly inspection verifies the JIT hoists them from the loop and retains fused operations. // The formula and rounding order remain identical to the narrower overloads. Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y); - Vector512 g = Vector512_.MultiplyAddEstimate( - cr, - Vector512.Create(-GCrMult), - Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y)); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y)); Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y); c0 = Vector512_.RoundToNearestInteger(r) * scale; @@ -162,17 +125,7 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - float r, - float g, - float b, - float maximumValue, - float halfValue, - float scale, - out float c0, - out float c1, - out float c2, - out float c3) + public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) { // The RGB inputs are unnormalized 0..255 encoder lanes. The BT.601 luma weights form Y, // while the signed chroma projections are biased by halfValue into the JPEG sample domain. @@ -185,104 +138,43 @@ internal abstract partial class JpegColorConverterBase /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector128 r, - Vector128 g, - Vector128 b, - Vector128 maximumValue, - Vector128 halfValue, - Vector128 scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3) + public static void ConvertFromRgb(Vector128 r, Vector128 g, Vector128 b, Vector128 maximumValue, Vector128 halfValue, Vector128 scale, out Vector128 c0, out Vector128 c1, out Vector128 c2, out Vector128 c3) { // Each vector holds four consecutive values from one RGB plane. The nested multiply-add sequence // produces four Y lanes, four Cb lanes, and four Cr lanes without transposition. The association // exposes two FMA opportunities per output while preserving the scalar formula's term grouping. - c0 = Vector128_.MultiplyAddEstimate( - Vector128.Create(0.299F), - r, - Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); - c1 = halfValue + Vector128_.MultiplyAddEstimate( - Vector128.Create(-0.168736F), - r, - Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)); - c2 = halfValue + Vector128_.MultiplyAddEstimate( - Vector128.Create(0.5F), - r, - Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)); + c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); + c1 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)); + c2 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)); c3 = default; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector256 r, - Vector256 g, - Vector256 b, - Vector256 maximumValue, - Vector256 halfValue, - Vector256 scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3) + public static void ConvertFromRgb(Vector256 r, Vector256 g, Vector256 b, Vector256 maximumValue, Vector256 halfValue, Vector256 scale, out Vector256 c0, out Vector256 c1, out Vector256 c2, out Vector256 c3) { // Eight planar RGB samples use the identical association as Vector128, allowing direct YMM FMA // generation while preserving the component-per-vector output layout. - c0 = Vector256_.MultiplyAddEstimate( - Vector256.Create(0.299F), - r, - Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); - c1 = halfValue + Vector256_.MultiplyAddEstimate( - Vector256.Create(-0.168736F), - r, - Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)); - c2 = halfValue + Vector256_.MultiplyAddEstimate( - Vector256.Create(0.5F), - r, - Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)); + c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); + c1 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)); + c2 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)); c3 = default; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvertFromRgb( - Vector512 r, - Vector512 g, - Vector512 b, - Vector512 maximumValue, - Vector512 halfValue, - Vector512 scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3) + public static void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3) { // Sixteen planar RGB samples use the same nested form. Constants are lane broadcasts and c3 is // deliberately zero because the shared traversal removes the unused fourth store for this operator. - c0 = Vector512_.MultiplyAddEstimate( - Vector512.Create(0.299F), - r, - Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); - c1 = halfValue + Vector512_.MultiplyAddEstimate( - Vector512.Create(-0.168736F), - r, - Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)); - c2 = halfValue + Vector512_.MultiplyAddEstimate( - Vector512.Create(0.5F), - r, - Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); + c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); + c1 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)); + c2 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); c3 = default; } /// - public static void ConvertToRgbInPlaceWithIcc( - Configuration configuration, - IccProfile profile, - in ComponentValues values, - float maximumValue) + public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) { using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); Span packed = memoryOwner.Memory.Span; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs index fb85194e6..003c77227 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs @@ -103,6 +103,7 @@ internal abstract partial class JpegColorConverterBase // CMYK extraction supplies inverted chromatic samples and K. Reflecting the first three results // reconstructs the chromatic RGB that YCbCr encodes, while K passes through untouched. CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out float c, out float m, out float y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); } @@ -112,6 +113,7 @@ internal abstract partial class JpegColorConverterBase { // Static constrained calls inline both stages, keeping four pixels in registers without materializing CMYK planes. CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector128 c, out Vector128 m, out Vector128 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); } @@ -121,6 +123,7 @@ internal abstract partial class JpegColorConverterBase { // Eight pixels flow through CMYK extraction and YCbCr projection entirely in YMM registers. CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector256 c, out Vector256 m, out Vector256 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); } @@ -130,6 +133,7 @@ internal abstract partial class JpegColorConverterBase { // Sixteen pixels flow through both mathematical stages in registers without materializing intermediate planes. CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector512 c, out Vector512 m, out Vector512 y, out c3); + YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 26e4c3584..36adf8f75 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -2,9 +2,6 @@ // Licensed under the Six Labors Split License. #nullable disable -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -101,124 +98,6 @@ internal abstract partial class JpegColorConverterBase /// Blue colors lane. public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); - public static void PackedNormalizeInterleave3( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - Span packed, - float scale) - { - DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 3; - Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale; - } - } - - public static void UnpackDeinterleave3( - ReadOnlySpan packed, - Span xLane, - Span yLane, - Span zLane) - { - DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - - // TODO: Investigate SIMD version of this. - ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - - for (nuint i = 0; i < (nuint)packed.Length; i++) - { - nuint baseIdx = i * 3; - Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx); - Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 1); - Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 2); - } - } - - public static void PackedNormalizeInterleave4( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - ReadOnlySpan wLane, - Span packed, - float maxValue) - { - DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); - - float scale = 1F / maxValue; - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 4; - Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale; - Unsafe.Add(ref packedRef, baseIdx + 3) = Unsafe.Add(ref wLaneRef, i) * scale; - } - } - - public static void PackedInvertNormalizeInterleave4( - ReadOnlySpan xLane, - ReadOnlySpan yLane, - ReadOnlySpan zLane, - ReadOnlySpan wLane, - Span packed, - float maxValue) - { - DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); - DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); - DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); - DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); - DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); - - float scale = 1F / maxValue; - - // TODO: Investigate SIMD version of this. - ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); - ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); - ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); - ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); - ref float packedRef = ref MemoryMarshal.GetReference(packed); - - for (nuint i = 0; i < (nuint)xLane.Length; i++) - { - nuint baseIdx = i * 4; - Unsafe.Add(ref packedRef, baseIdx) = (maxValue - Unsafe.Add(ref xLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 1) = (maxValue - Unsafe.Add(ref yLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 2) = (maxValue - Unsafe.Add(ref zLaneRef, i)) * scale; - Unsafe.Add(ref packedRef, baseIdx + 3) = (maxValue - Unsafe.Add(ref wLaneRef, i)) * scale; - } - } - /// /// Returns the s for all supported color spaces and precisions. /// @@ -382,6 +261,14 @@ internal abstract partial class JpegColorConverterBase this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : []; } + /// + /// Initializes a new instance of the struct from explicitly supplied planar spans. + /// + /// The number of populated component planes. + /// The first component plane. + /// The second component plane, if present. + /// The third component plane, if present. + /// The fourth component plane, if present. internal ComponentValues( int componentCount, Span c0, @@ -396,6 +283,12 @@ internal abstract partial class JpegColorConverterBase this.Component3 = c3; } + /// + /// Creates a view over the same component planes for the requested sample range. + /// + /// The zero-based sample offset. + /// The number of samples in each returned plane. + /// The sliced component values. public ComponentValues Slice(int start, int length) { Span c0 = this.Component0.Slice(start, length); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 177e9b44f..c68dd19b4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -116,6 +116,8 @@ internal class ComponentProcessor : IDisposable } static void SumVertical(Span target, Span source) + + // Exact destination overlap is supported, so each accumulated row remains in target. => TensorPrimitives_.Add(target, source, target); static void SumHorizontal(Span target, int factor) @@ -164,6 +166,8 @@ internal class ComponentProcessor : IDisposable } static void MultiplyToAverage(Span target, float multiplier) + + // Apply the subsampling reciprocal in place after all contributing rows have been summed. => TensorPrimitives_.Multiply(target, multiplier, target); } } diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 942a80ab7..2cd88d6bb 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -140,12 +140,7 @@ internal static class AverageFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, uint bytesPerPixel, out int sum) - => PngFilterEncoder.Encode( - scanline, - previousScanline, - result, - bytesPerPixel, - out sum); + => PngFilterEncoder.Encode(scanline, previousScanline, result, bytesPerPixel, out sum); /// /// Calculates the average value of two bytes diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs index 9fcec7b78..c81187775 100644 --- a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs +++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs @@ -3,8 +3,8 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Formats.Png.Filters; @@ -51,11 +51,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - public static abstract Vector128 Invoke( - Vector128 scan, - Vector128 left, - Vector128 above, - Vector128 upperLeft); + public static abstract Vector128 Invoke(Vector128 scan, Vector128 left, Vector128 above, Vector128 upperLeft); /// /// Filters thirty-two byte lanes from their PNG neighborhoods. @@ -65,11 +61,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - public static abstract Vector256 Invoke( - Vector256 scan, - Vector256 left, - Vector256 above, - Vector256 upperLeft); + public static abstract Vector256 Invoke(Vector256 scan, Vector256 left, Vector256 above, Vector256 upperLeft); /// /// Filters sixty-four byte lanes from their PNG neighborhoods. @@ -79,11 +71,7 @@ internal interface IPngFilterOperator /// The corresponding components in the preceding scanline. /// The preceding components in the preceding scanline. /// The filtered residuals. - public static abstract Vector512 Invoke( - Vector512 scan, - Vector512 left, - Vector512 above, - Vector512 upperLeft); + public static abstract Vector512 Invoke(Vector512 scan, Vector512 left, Vector512 above, Vector512 upperLeft); } /// @@ -109,29 +97,17 @@ internal readonly struct SubFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector128 Invoke( - Vector128 scan, - Vector128 left, - Vector128 above, - Vector128 upperLeft) + public static Vector128 Invoke(Vector128 scan, Vector128 left, Vector128 above, Vector128 upperLeft) => scan - left; /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 Invoke( - Vector256 scan, - Vector256 left, - Vector256 above, - Vector256 upperLeft) + public static Vector256 Invoke(Vector256 scan, Vector256 left, Vector256 above, Vector256 upperLeft) => scan - left; /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector512 Invoke( - Vector512 scan, - Vector512 left, - Vector512 above, - Vector512 upperLeft) + public static Vector512 Invoke(Vector512 scan, Vector512 left, Vector512 above, Vector512 upperLeft) => scan - left; } @@ -158,29 +134,17 @@ internal readonly struct UpFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector128 Invoke( - Vector128 scan, - Vector128 left, - Vector128 above, - Vector128 upperLeft) + public static Vector128 Invoke(Vector128 scan, Vector128 left, Vector128 above, Vector128 upperLeft) => scan - above; /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 Invoke( - Vector256 scan, - Vector256 left, - Vector256 above, - Vector256 upperLeft) + public static Vector256 Invoke(Vector256 scan, Vector256 left, Vector256 above, Vector256 upperLeft) => scan - above; /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector512 Invoke( - Vector512 scan, - Vector512 left, - Vector512 above, - Vector512 upperLeft) + public static Vector512 Invoke(Vector512 scan, Vector512 left, Vector512 above, Vector512 upperLeft) => scan - above; } @@ -208,11 +172,7 @@ internal readonly struct AverageFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector128 Invoke( - Vector128 scan, - Vector128 left, - Vector128 above, - Vector128 upperLeft) + public static Vector128 Invoke(Vector128 scan, Vector128 left, Vector128 above, Vector128 upperLeft) { Vector128 average; @@ -239,20 +199,18 @@ internal readonly struct AverageFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 Invoke( - Vector256 scan, - Vector256 left, - Vector256 above, - Vector256 upperLeft) + public static Vector256 Invoke(Vector256 scan, Vector256 left, Vector256 above, Vector256 upperLeft) + + // VPAVGB rounds (left + above) / 2 upward. Complementing both inputs and + // the result changes that to the truncated average required by PNG. => scan - ~Avx2.Average(~left, ~above); /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector512 Invoke( - Vector512 scan, - Vector512 left, - Vector512 above, - Vector512 upperLeft) + public static Vector512 Invoke(Vector512 scan, Vector512 left, Vector512 above, Vector512 upperLeft) + + // AVX-512BW retains VPAVGB's upward rounding, so use the same complement + // identity as AVX2 to obtain floor((left + above) / 2) in every byte lane. => scan - ~Avx512BW.Average(~left, ~above); } @@ -283,20 +241,14 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator int distanceUpperLeft = Numerics.Abs(p - upperLeft); // PNG resolves equal distances in left, above, upper-left order. - byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft - ? left - : distanceAbove <= distanceUpperLeft ? above : upperLeft; + byte predictor = distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft ? left : distanceAbove <= distanceUpperLeft ? above : upperLeft; return (byte)(scan - predictor); } /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector128 Invoke( - Vector128 scan, - Vector128 left, - Vector128 above, - Vector128 upperLeft) + public static Vector128 Invoke(Vector128 scan, Vector128 left, Vector128 above, Vector128 upperLeft) { Vector128 predictor = Predict(left, above, upperLeft); return scan - predictor; @@ -304,11 +256,7 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 Invoke( - Vector256 scan, - Vector256 left, - Vector256 above, - Vector256 upperLeft) + public static Vector256 Invoke(Vector256 scan, Vector256 left, Vector256 above, Vector256 upperLeft) { Vector256 predictor = Predict(left, above, upperLeft); return scan - predictor; @@ -316,11 +264,7 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator /// [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector512 Invoke( - Vector512 scan, - Vector512 left, - Vector512 above, - Vector512 upperLeft) + public static Vector512 Invoke(Vector512 scan, Vector512 left, Vector512 above, Vector512 upperLeft) { Vector512 predictor = Predict(left, above, upperLeft); return scan - predictor; @@ -329,196 +273,151 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator /// /// Selects the nearest Paeth neighbor for sixteen independent byte lanes. /// + /// The reconstructed component immediately before each current component. + /// The reconstructed component immediately above each current component. + /// The reconstructed component diagonally above and before each current component. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector128 Predict( - Vector128 left, - Vector128 above, - Vector128 upperLeft) + private static Vector128 Predict(Vector128 left, Vector128 above, Vector128 upperLeft) { - Vector128 aboveMinusUpper = SubtractSaturate(above, upperLeft); - Vector128 leftMinusUpper = SubtractSaturate(left, upperLeft); - Vector128 distanceLeft = SubtractSaturate(upperLeft, above) | aboveMinusUpper; - Vector128 distanceAbove = SubtractSaturate(upperLeft, left) | leftMinusUpper; - - return SelectPredictor( - left, - above, - upperLeft, - aboveMinusUpper, - leftMinusUpper, - distanceLeft, - distanceAbove); + // For p = left + above - upperLeft, the Paeth distances simplify to: + // distanceLeft = |above - upperLeft| + // distanceAbove = |left - upperLeft| + // Computing both unsigned subtraction directions and OR-ing them obtains + // each absolute difference without widening the byte lanes. + Vector128 aboveMinusUpper = Vector128_.SubtractSaturate(above, upperLeft); + Vector128 leftMinusUpper = Vector128_.SubtractSaturate(left, upperLeft); + Vector128 distanceLeft = Vector128_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector128 distanceAbove = Vector128_.SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); } /// /// Selects the nearest Paeth neighbor for thirty-two independent byte lanes. /// + /// The reconstructed component immediately before each current component. + /// The reconstructed component immediately above each current component. + /// The reconstructed component diagonally above and before each current component. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector256 Predict( - Vector256 left, - Vector256 above, - Vector256 upperLeft) + private static Vector256 Predict(Vector256 left, Vector256 above, Vector256 upperLeft) { - Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); - Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); - Vector256 distanceLeft = Avx2.SubtractSaturate(upperLeft, above) | aboveMinusUpper; - Vector256 distanceAbove = Avx2.SubtractSaturate(upperLeft, left) | leftMinusUpper; - - return SelectPredictor( - left, - above, - upperLeft, - aboveMinusUpper, - leftMinusUpper, - distanceLeft, - distanceAbove); + // Apply the same Paeth identities as the 128-bit path to thirty-two lanes. + // Saturating subtraction in both directions forms the absolute differences + // without widening, preserving one predictor result per source byte. + Vector256 aboveMinusUpper = Vector256_.SubtractSaturate(above, upperLeft); + Vector256 leftMinusUpper = Vector256_.SubtractSaturate(left, upperLeft); + Vector256 distanceLeft = Vector256_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector256 distanceAbove = Vector256_.SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); } /// /// Selects the nearest Paeth neighbor for sixty-four independent byte lanes. /// + /// The reconstructed component immediately before each current component. + /// The reconstructed component immediately above each current component. + /// The reconstructed component diagonally above and before each current component. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector512 Predict( - Vector512 left, - Vector512 above, - Vector512 upperLeft) + private static Vector512 Predict(Vector512 left, Vector512 above, Vector512 upperLeft) { - Vector512 aboveMinusUpper = Avx512BW.SubtractSaturate(above, upperLeft); - Vector512 leftMinusUpper = Avx512BW.SubtractSaturate(left, upperLeft); - Vector512 distanceLeft = Avx512BW.SubtractSaturate(upperLeft, above) | aboveMinusUpper; - Vector512 distanceAbove = Avx512BW.SubtractSaturate(upperLeft, left) | leftMinusUpper; - - return SelectPredictor( - left, - above, - upperLeft, - aboveMinusUpper, - leftMinusUpper, - distanceLeft, - distanceAbove); + // Apply the same byte-lane Paeth identities to sixty-four AVX-512BW lanes. + // No cross-lane operation is required because every component has its own + // left, above, and upper-left inputs at the matching vector index. + Vector512 aboveMinusUpper = Vector512_.SubtractSaturate(above, upperLeft); + Vector512 leftMinusUpper = Vector512_.SubtractSaturate(left, upperLeft); + Vector512 distanceLeft = Vector512_.SubtractSaturate(upperLeft, above) | aboveMinusUpper; + Vector512 distanceAbove = Vector512_.SubtractSaturate(upperLeft, left) | leftMinusUpper; + + return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove); } /// /// Applies Paeth distance and tie-breaking rules to sixteen lanes. /// + /// The left-neighbor candidates. + /// The above-neighbor candidates. + /// The upper-left-neighbor candidates. + /// The saturated differences from above to upper-left. + /// The saturated differences from left to upper-left. + /// The Paeth distances for the left candidates. + /// The Paeth distances for the above candidates. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector128 SelectPredictor( - Vector128 left, - Vector128 above, - Vector128 upperLeft, - Vector128 aboveMinusUpper, - Vector128 leftMinusUpper, - Vector128 distanceLeft, - Vector128 distanceAbove) + private static Vector128 SelectPredictor(Vector128 left, Vector128 above, Vector128 upperLeft, Vector128 aboveMinusUpper, Vector128 leftMinusUpper, Vector128 distanceLeft, Vector128 distanceAbove) { - Vector128 sameDirection = Vector128.Equals( - Vector128.Equals(aboveMinusUpper, Vector128.Zero), - Vector128.Equals(leftMinusUpper, Vector128.Zero)); + Vector128 sameDirection = Vector128.Equals(Vector128.Equals(aboveMinusUpper, Vector128.Zero), Vector128.Equals(leftMinusUpper, Vector128.Zero)); - Vector128 distanceUpper = sameDirection - | SubtractSaturate(distanceAbove, distanceLeft) - | SubtractSaturate(distanceLeft, distanceAbove); + // If left and above lie on the same side of upper-left, distanceUpper is + // their summed distance and cannot beat either neighbor; the all-bits mask + // excludes upper-left. On opposite sides, that distance is the absolute + // difference between distanceLeft and distanceAbove. + Vector128 distanceUpper = sameDirection | Vector128_.SubtractSaturate(distanceAbove, distanceLeft) | Vector128_.SubtractSaturate(distanceLeft, distanceAbove); + // Equality selects above before upper-left, implementing PNG's second tie rule. Vector128 minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove); - Vector128 aboveOrUpper = Vector128.ConditionalSelect( - Vector128.Equals(minimumAboveUpper, distanceAbove), - above, - upperLeft); + Vector128 aboveOrUpper = Vector128.ConditionalSelect(Vector128.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); // Applying the left comparison last preserves PNG's left-first tie rule. - return Vector128.ConditionalSelect( - Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft), - left, - aboveOrUpper); + return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); } /// /// Applies Paeth distance and tie-breaking rules to thirty-two lanes. /// + /// The left-neighbor candidates. + /// The above-neighbor candidates. + /// The upper-left-neighbor candidates. + /// The saturated differences from above to upper-left. + /// The saturated differences from left to upper-left. + /// The Paeth distances for the left candidates. + /// The Paeth distances for the above candidates. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector256 SelectPredictor( - Vector256 left, - Vector256 above, - Vector256 upperLeft, - Vector256 aboveMinusUpper, - Vector256 leftMinusUpper, - Vector256 distanceLeft, - Vector256 distanceAbove) + private static Vector256 SelectPredictor(Vector256 left, Vector256 above, Vector256 upperLeft, Vector256 aboveMinusUpper, Vector256 leftMinusUpper, Vector256 distanceLeft, Vector256 distanceAbove) { - Vector256 sameDirection = Vector256.Equals( - Vector256.Equals(aboveMinusUpper, Vector256.Zero), - Vector256.Equals(leftMinusUpper, Vector256.Zero)); + Vector256 sameDirection = Vector256.Equals(Vector256.Equals(aboveMinusUpper, Vector256.Zero), Vector256.Equals(leftMinusUpper, Vector256.Zero)); - Vector256 distanceUpper = sameDirection - | Avx2.SubtractSaturate(distanceAbove, distanceLeft) - | Avx2.SubtractSaturate(distanceLeft, distanceAbove); + // Exclude upper-left when its distance is the non-minimal sum; otherwise + // compute its distance as the absolute difference of the two known distances. + Vector256 distanceUpper = sameDirection | Vector256_.SubtractSaturate(distanceAbove, distanceLeft) | Vector256_.SubtractSaturate(distanceLeft, distanceAbove); + // Select above on equality, then select left on equality to preserve PNG's + // required left, above, upper-left tie order in every byte lane. Vector256 minimumAboveUpper = Vector256.Min(distanceUpper, distanceAbove); - Vector256 aboveOrUpper = Vector256.ConditionalSelect( - Vector256.Equals(minimumAboveUpper, distanceAbove), - above, - upperLeft); - - return Vector256.ConditionalSelect( - Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft), - left, - aboveOrUpper); + Vector256 aboveOrUpper = Vector256.ConditionalSelect(Vector256.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); + + return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); } /// /// Applies Paeth distance and tie-breaking rules to sixty-four lanes. /// + /// The left-neighbor candidates. + /// The above-neighbor candidates. + /// The upper-left-neighbor candidates. + /// The saturated differences from above to upper-left. + /// The saturated differences from left to upper-left. + /// The Paeth distances for the left candidates. + /// The Paeth distances for the above candidates. + /// The selected Paeth predictor for each byte lane. [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector512 SelectPredictor( - Vector512 left, - Vector512 above, - Vector512 upperLeft, - Vector512 aboveMinusUpper, - Vector512 leftMinusUpper, - Vector512 distanceLeft, - Vector512 distanceAbove) + private static Vector512 SelectPredictor(Vector512 left, Vector512 above, Vector512 upperLeft, Vector512 aboveMinusUpper, Vector512 leftMinusUpper, Vector512 distanceLeft, Vector512 distanceAbove) { - Vector512 sameDirection = Vector512.Equals( - Vector512.Equals(aboveMinusUpper, Vector512.Zero), - Vector512.Equals(leftMinusUpper, Vector512.Zero)); + Vector512 sameDirection = Vector512.Equals(Vector512.Equals(aboveMinusUpper, Vector512.Zero), Vector512.Equals(leftMinusUpper, Vector512.Zero)); - Vector512 distanceUpper = sameDirection - | Avx512BW.SubtractSaturate(distanceAbove, distanceLeft) - | Avx512BW.SubtractSaturate(distanceLeft, distanceAbove); + // Exclude upper-left when its distance is the non-minimal sum; otherwise + // compute its distance as the absolute difference of the two known distances. + Vector512 distanceUpper = sameDirection | Vector512_.SubtractSaturate(distanceAbove, distanceLeft) | Vector512_.SubtractSaturate(distanceLeft, distanceAbove); + // Select above on equality, then select left on equality to preserve PNG's + // required left, above, upper-left tie order in every byte lane. Vector512 minimumAboveUpper = Vector512.Min(distanceUpper, distanceAbove); - Vector512 aboveOrUpper = Vector512.ConditionalSelect( - Vector512.Equals(minimumAboveUpper, distanceAbove), - above, - upperLeft); - - return Vector512.ConditionalSelect( - Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft), - left, - aboveOrUpper); - } - - /// - /// Performs an unsigned saturating subtraction using the active 128-bit instruction set. - /// - /// The minuend lanes. - /// The subtrahend lanes. - /// The saturated lane-wise differences. - [MethodImpl(InliningOptions.AlwaysInline)] - private static Vector128 SubtractSaturate(Vector128 left, Vector128 right) - { - if (Sse2.IsSupported) - { - return Sse2.SubtractSaturate(left, right); - } - - if (AdvSimd.IsSupported) - { - return AdvSimd.SubtractSaturate(left, right); - } + Vector512 aboveOrUpper = Vector512.ConditionalSelect(Vector512.Equals(minimumAboveUpper, distanceAbove), above, upperLeft); - // Subtracting the smaller operand produces max(left - right, 0) without - // requiring a backend-specific saturating-subtract instruction. - return left - Vector128.Min(left, right); + return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumAboveUpper, distanceLeft), distanceLeft), left, aboveOrUpper); } } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 0216a2627..eaeaaf173 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -192,12 +192,7 @@ internal static class PaethFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) - => PngFilterEncoder.Encode( - scanline, - previousScanline, - result, - (uint)bytesPerPixel, - out sum); + => PngFilterEncoder.Encode(scanline, previousScanline, result, (uint)bytesPerPixel, out sum); /// /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses diff --git a/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs b/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs index 4c298e0fc..3e4386d93 100644 --- a/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs +++ b/src/ImageSharp/Formats/Png/Filters/PngFilterEncoder.cs @@ -25,12 +25,7 @@ internal static class PngFilterEncoder // Inlining closes every static interface call over TOperator. The JIT can then remove // source loads ignored by simpler predictors and specialize the active register widths. [MethodImpl(InliningOptions.AlwaysInline)] - public static void Encode( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - uint bytesPerPixel, - out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, uint bytesPerPixel, out int sum) where TOperator : struct, IPngFilterOperator { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); @@ -51,11 +46,7 @@ internal static class PngFilterEncoder { byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; - byte filtered = TOperator.Invoke( - Unsafe.Add(ref scanBaseRef, x), - 0, - above, - 0); + byte filtered = TOperator.Invoke(Unsafe.Add(ref scanBaseRef, x), 0, above, 0); Unsafe.Add(ref resultBaseRef, x + 1) = filtered; sum += Numerics.Abs(unchecked((sbyte)filtered)); @@ -76,28 +67,19 @@ internal static class PngFilterEncoder // four input vectors retain the scan/left/above/upper-left PNG layout. // Operator usage flags are constants after generic specialization, so // unused predictors do not retain even fault-preserving probe loads. - Vector512 left = TOperator.UsesLeft - ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) - : default; - Vector512 above = TOperator.UsesAbove - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) - : default; - Vector512 upperLeft = TOperator.UsesUpperLeft - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) - : default; - - Vector512 filtered = TOperator.Invoke( - Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), - left, - above, - upperLeft); + Vector512 left = TOperator.UsesLeft ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; + Vector512 above = TOperator.UsesAbove ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) : default; + Vector512 upperLeft = TOperator.UsesUpperLeft ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; + + Vector512 filtered = TOperator.Invoke(Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; x += (uint)Vector512.Count; - // PNG scores each residual as abs((sbyte)residual). VPSADBW sums eight - // byte lanes into every other 32-bit lane without widening each byte. - Vector512 absolute = Avx512BW.Abs(filtered.AsSByte()); + // Vector512.Abs lowers to VPABSB under the surrounding AVX-512BW guard. + // Reinterpreting the signed result preserves -128's 0x80 bit pattern as + // the unsigned magnitude 128 consumed by VPSADBW. + Vector512 absolute = Vector512.Abs(filtered.AsSByte()).AsByte(); sum512 += Avx512BW.SumAbsoluteDifferences(absolute, Vector512.Zero).AsUInt32(); } @@ -114,29 +96,26 @@ internal static class PngFilterEncoder for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector256.Count) { - Vector256 left = TOperator.UsesLeft - ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) - : default; - Vector256 above = TOperator.UsesAbove - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) - : default; - Vector256 upperLeft = TOperator.UsesUpperLeft - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) - : default; - - Vector256 filtered = TOperator.Invoke( - Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), - left, - above, - upperLeft); + // Thirty-two byte lanes preserve the same scan/left/above/upper-left + // correspondence as the 512-bit path. Closed operator flags remove + // unused loads when the predictor does not consume that neighbor. + Vector256 left = TOperator.UsesLeft ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; + Vector256 above = TOperator.UsesAbove ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) : default; + Vector256 upperLeft = TOperator.UsesUpperLeft ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; + + Vector256 filtered = TOperator.Invoke(Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; x += (uint)Vector256.Count; - Vector256 absolute = Avx2.Abs(filtered.AsSByte()); + // Vector256.Abs lowers to VPABSB under the surrounding AVX2 guard. + // Reinterpreting the signed result preserves -128's 0x80 bit pattern as + // the unsigned magnitude 128 consumed by VPSADBW. + Vector256 absolute = Vector256.Abs(filtered.AsSByte()).AsByte(); sum256 += Avx2.SumAbsoluteDifferences(absolute, Vector256.Zero).AsUInt32(); } + // Fold both 128-bit halves into the shared four-lane accumulator. sum128 += sum256.GetLower() + sum256.GetUpper(); } @@ -146,29 +125,24 @@ internal static class PngFilterEncoder for (nuint xLeft = x - bytesPerPixel; (int)x <= oneRegisterFromEnd; xLeft += (uint)Vector128.Count) { - Vector128 left = TOperator.UsesLeft - ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) - : default; - Vector128 above = TOperator.UsesAbove - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) - : default; - Vector128 upperLeft = TOperator.UsesUpperLeft - ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) - : default; - - Vector128 filtered = TOperator.Invoke( - Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), - left, - above, - upperLeft); + // The final vector width handles sixteen more components with the + // same lane-wise neighborhood layout before the scalar remainder. + Vector128 left = TOperator.UsesLeft ? Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)) : default; + Vector128 above = TOperator.UsesAbove ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)) : default; + Vector128 upperLeft = TOperator.UsesUpperLeft ? Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)) : default; + + Vector128 filtered = TOperator.Invoke(Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)), left, above, upperLeft); Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = filtered; x += (uint)Vector128.Count; + // AccumulateAbsolute selects the available x86 or portable widening + // reduction while preserving the same unsigned 32-bit partial sums. sum128 = AccumulateAbsolute(sum128, filtered); } } + // Reduce the four partial lanes before adding individually scored tail bytes. sum += unchecked((int)Vector128.Sum(sum128)); for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++, x++) @@ -177,11 +151,7 @@ internal static class PngFilterEncoder byte above = TOperator.UsesAbove ? Unsafe.Add(ref previousBaseRef, x) : (byte)0; byte upperLeft = TOperator.UsesUpperLeft ? Unsafe.Add(ref previousBaseRef, xLeft) : (byte)0; - byte filtered = TOperator.Invoke( - Unsafe.Add(ref scanBaseRef, x), - left, - above, - upperLeft); + byte filtered = TOperator.Invoke(Unsafe.Add(ref scanBaseRef, x), left, above, upperLeft); Unsafe.Add(ref resultBaseRef, x + 1) = filtered; sum += Numerics.Abs(unchecked((sbyte)filtered)); @@ -197,27 +167,16 @@ internal static class PngFilterEncoder [MethodImpl(InliningOptions.AlwaysInline)] private static Vector128 AccumulateAbsolute(Vector128 accumulator, Vector128 residuals) { + // The generic absolute-value intrinsic selects PABSB where available and + // preserves -128's 0x80 bit pattern as the unsigned magnitude 128. + Vector128 absolute = Vector128.Abs(residuals.AsSByte()).AsByte(); + if (Sse2.IsSupported) { - Vector128 absolute; - - if (Ssse3.IsSupported) - { - absolute = Ssse3.Abs(residuals.AsSByte()); - } - else - { - // SSE2 has no packed signed-byte absolute instruction. The sign mask - // implements (value + mask) XOR mask, including -128 -> 128. - Vector128 mask = Sse2.CompareGreaterThan(Vector128.Zero, residuals.AsSByte()); - absolute = Sse2.Xor(Sse2.Add(residuals.AsSByte(), mask), mask).AsByte(); - } - return accumulator + Sse2.SumAbsoluteDifferences(absolute, Vector128.Zero).AsUInt32(); } - Vector128 absoluteArm = Vector128.Abs(residuals.AsSByte()).AsByte(); - (Vector128 lower16, Vector128 upper16) = Vector128.Widen(absoluteArm); + (Vector128 lower16, Vector128 upper16) = Vector128.Widen(absolute); (Vector128 lower0, Vector128 lower1) = Vector128.Widen(lower16); (Vector128 upper0, Vector128 upper1) = Vector128.Widen(upper16); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 957fb670f..946b59022 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -102,7 +102,7 @@ internal static class SubFilter } /// - /// Encodes a scanline with the sup filter applied. + /// Encodes a scanline with the sub filter applied. /// /// The scanline to encode. /// The filtered scanline result. @@ -110,10 +110,8 @@ internal static class SubFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) - => PngFilterEncoder.Encode( - scanline, - scanline, - result, - (uint)bytesPerPixel, - out sum); + + // Sub does not consume an above neighbor, so the shared traversal may alias + // the unused previous-row argument to the current row without an extra buffer. + => PngFilterEncoder.Encode(scanline, scanline, result, (uint)bytesPerPixel, out sum); } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 5f78833cd..d64a1ea51 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -36,10 +36,8 @@ internal static class UpFilter /// The sum of the total variance of the filtered row. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) - => PngFilterEncoder.Encode( - scanline, - previousScanline, - result, - 0, - out sum); + + // Up never reads a left neighbor, so bytesPerPixel is deliberately zero in + // the shared traversal and no unsigned left offset is evaluated. + => PngFilterEncoder.Encode(scanline, previousScanline, result, 0, out sum); } diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index 7c3562cb2..bbe9748fc 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -363,6 +363,7 @@ internal class AlphaDecoder : IDisposable } else { + // Byte addition intentionally wraps modulo 256, matching the WebP alpha predictor. TensorPrimitives_.Add(input[..width], prev[..width], dst[..width]); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index fc3ecdd94..c59235e70 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -331,7 +331,7 @@ internal abstract unsafe class Vp8LHistogram { if (b.IsUsed(0)) { - AddVector(this.Literal, b.Literal, output.Literal, literalSize); + TensorPrimitives_.Add(this.Literal[..literalSize], b.Literal[..literalSize], output.Literal[..literalSize]); } else { @@ -354,7 +354,7 @@ internal abstract unsafe class Vp8LHistogram { if (b.IsUsed(1)) { - AddVector(this.Red, b.Red, output.Red, size); + TensorPrimitives_.Add(this.Red[..size], b.Red[..size], output.Red[..size]); } else { @@ -377,7 +377,7 @@ internal abstract unsafe class Vp8LHistogram { if (b.IsUsed(2)) { - AddVector(this.Blue, b.Blue, output.Blue, size); + TensorPrimitives_.Add(this.Blue[..size], b.Blue[..size], output.Blue[..size]); } else { @@ -400,7 +400,7 @@ internal abstract unsafe class Vp8LHistogram { if (b.IsUsed(3)) { - AddVector(this.Alpha, b.Alpha, output.Alpha, size); + TensorPrimitives_.Add(this.Alpha[..size], b.Alpha[..size], output.Alpha[..size]); } else { @@ -423,7 +423,7 @@ internal abstract unsafe class Vp8LHistogram { if (b.IsUsed(4)) { - AddVector(this.Distance, b.Distance, output.Distance, size); + TensorPrimitives_.Add(this.Distance[..size], b.Distance[..size], output.Distance[..size]); } else { @@ -534,14 +534,6 @@ internal abstract unsafe class Vp8LHistogram return cost; } - private static void AddVector(Span a, Span b, Span output, int count) - { - DebugGuard.MustBeGreaterThanOrEqualTo(a.Length, count, nameof(a.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); - - TensorPrimitives_.Add(a[..count], b[..count], output[..count]); - } } internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs index 85313dc20..d31e07c62 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -22,17 +22,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); } @@ -46,17 +40,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); } @@ -70,17 +58,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); } @@ -94,17 +76,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); } @@ -118,17 +94,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); } @@ -142,17 +112,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); } @@ -166,17 +130,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); } @@ -190,17 +148,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); } @@ -214,17 +166,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); } @@ -238,17 +184,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); } @@ -262,17 +202,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); } @@ -286,17 +220,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); } @@ -310,17 +238,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); } @@ -334,17 +256,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); } @@ -358,17 +274,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); } @@ -382,17 +292,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); } @@ -406,17 +310,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); } @@ -430,17 +328,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); } @@ -454,17 +346,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); } @@ -478,17 +364,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); } @@ -502,17 +382,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); } @@ -526,17 +400,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); } @@ -550,17 +418,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); } @@ -574,17 +436,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); } @@ -598,17 +454,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); } @@ -622,17 +472,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); } @@ -646,17 +490,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); } @@ -670,17 +508,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); } @@ -694,17 +526,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); } @@ -718,17 +544,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); } @@ -742,17 +562,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); } @@ -766,17 +580,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); } @@ -790,17 +598,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); } @@ -814,17 +616,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); } @@ -838,17 +634,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); } @@ -862,17 +652,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); } @@ -886,17 +670,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); } @@ -910,17 +688,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); } @@ -934,17 +706,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); } @@ -958,17 +724,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); } @@ -982,17 +742,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); } @@ -1006,17 +760,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); } @@ -1030,17 +778,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); } @@ -1054,17 +796,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); } @@ -1078,17 +814,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); } @@ -1102,17 +832,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); } @@ -1126,17 +850,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); } @@ -1150,17 +868,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); } @@ -1174,17 +886,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); } @@ -1198,17 +904,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); } @@ -1222,17 +922,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); } @@ -1246,17 +940,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); } @@ -1270,17 +958,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); } @@ -1294,17 +976,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); } @@ -1318,17 +994,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); } @@ -1342,17 +1012,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); } @@ -1366,17 +1030,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); } @@ -1390,17 +1048,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); } @@ -1414,17 +1066,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); } @@ -1438,17 +1084,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); } @@ -1462,17 +1102,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); } @@ -1486,17 +1120,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); } @@ -1510,17 +1138,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); } @@ -1534,17 +1156,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); } @@ -1558,17 +1174,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); } @@ -1582,17 +1192,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); } @@ -1606,17 +1210,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); } @@ -1630,17 +1228,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); } @@ -1654,17 +1246,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); } @@ -1678,17 +1264,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); } @@ -1702,17 +1282,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); } @@ -1726,17 +1300,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); } @@ -1750,17 +1318,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); } @@ -1774,17 +1336,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); } @@ -1798,17 +1354,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); } @@ -1822,17 +1372,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); } @@ -1846,17 +1390,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); } @@ -1870,17 +1408,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); } @@ -1894,17 +1426,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); } @@ -1918,17 +1444,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); } @@ -1942,17 +1462,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); } @@ -1966,17 +1480,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); } @@ -1990,17 +1498,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); } @@ -2014,17 +1516,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); } @@ -2038,17 +1534,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); } @@ -2062,17 +1552,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); } @@ -2086,17 +1570,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); } @@ -2110,17 +1588,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); } @@ -2134,17 +1606,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); } @@ -2158,17 +1624,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); } @@ -2182,17 +1642,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); } @@ -2206,17 +1660,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); } @@ -2230,17 +1678,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); } @@ -2254,17 +1696,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); } @@ -2278,17 +1714,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); } @@ -2302,17 +1732,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); } @@ -2326,17 +1750,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); } @@ -2350,17 +1768,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); } @@ -2374,17 +1786,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); } @@ -2398,17 +1804,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); } @@ -2422,17 +1822,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); } @@ -2446,17 +1840,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); } @@ -2470,17 +1858,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); } @@ -2494,17 +1876,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); } @@ -2518,17 +1894,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); } @@ -2542,17 +1912,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); } @@ -2566,17 +1930,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); } @@ -2590,17 +1948,11 @@ internal static class AssociatedAlphaPixelBlenderOperators => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt index 70607feb1..801d76328 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt @@ -66,17 +66,11 @@ foreach (var composer in composers) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs index 00427308c..81a3f24a8 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs @@ -20,10 +20,7 @@ internal abstract class AssociatedAlphaPixelBlender : PixelBl public sealed override TPixel Blend(TPixel background, TPixel source, float amount) { // Associated RGB and alpha must remain in their stored representation throughout composition. - Vector4 result = TOperator.Invoke( - background.ToAssociatedScaledVector4(), - source.ToAssociatedScaledVector4(), - Numerics.Clamp(amount, 0, 1F)); + Vector4 result = TOperator.Invoke(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1F)); return TPixel.FromAssociatedScaledVector4(result); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 250d789dd..2ff4660b7 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -22,17 +22,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalSrc(background, source, amount); } @@ -46,17 +40,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplySrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplySrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplySrc(background, source, amount); } @@ -70,17 +58,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddSrc(background, source, amount); } @@ -94,17 +76,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractSrc(background, source, amount); } @@ -118,17 +94,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenSrc(background, source, amount); } @@ -142,17 +112,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenSrc(background, source, amount); } @@ -166,17 +130,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenSrc(background, source, amount); } @@ -190,17 +148,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlaySrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlaySrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlaySrc(background, source, amount); } @@ -214,17 +166,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightSrc(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightSrc(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightSrc(background, source, amount); } @@ -238,17 +184,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalSrcAtop(background, source, amount); } @@ -262,17 +202,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); } @@ -286,17 +220,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddSrcAtop(background, source, amount); } @@ -310,17 +238,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); } @@ -334,17 +256,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); } @@ -358,17 +274,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); } @@ -382,17 +292,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenSrcAtop(background, source, amount); } @@ -406,17 +310,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); } @@ -430,17 +328,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); } @@ -454,17 +346,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalSrcOver(background, source, amount); } @@ -478,17 +364,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplySrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplySrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplySrcOver(background, source, amount); } @@ -502,17 +382,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddSrcOver(background, source, amount); } @@ -526,17 +400,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractSrcOver(background, source, amount); } @@ -550,17 +418,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenSrcOver(background, source, amount); } @@ -574,17 +436,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenSrcOver(background, source, amount); } @@ -598,17 +454,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenSrcOver(background, source, amount); } @@ -622,17 +472,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlaySrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlaySrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlaySrcOver(background, source, amount); } @@ -646,17 +490,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightSrcOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightSrcOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightSrcOver(background, source, amount); } @@ -670,17 +508,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalSrcIn(background, source, amount); } @@ -694,17 +526,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplySrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplySrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplySrcIn(background, source, amount); } @@ -718,17 +544,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddSrcIn(background, source, amount); } @@ -742,17 +562,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractSrcIn(background, source, amount); } @@ -766,17 +580,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenSrcIn(background, source, amount); } @@ -790,17 +598,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenSrcIn(background, source, amount); } @@ -814,17 +616,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenSrcIn(background, source, amount); } @@ -838,17 +634,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlaySrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlaySrcIn(background, source, amount); } @@ -862,17 +652,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightSrcIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightSrcIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightSrcIn(background, source, amount); } @@ -886,17 +670,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalSrcOut(background, source, amount); } @@ -910,17 +688,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplySrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplySrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplySrcOut(background, source, amount); } @@ -934,17 +706,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddSrcOut(background, source, amount); } @@ -958,17 +724,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractSrcOut(background, source, amount); } @@ -982,17 +742,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenSrcOut(background, source, amount); } @@ -1006,17 +760,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenSrcOut(background, source, amount); } @@ -1030,17 +778,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenSrcOut(background, source, amount); } @@ -1054,17 +796,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlaySrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlaySrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlaySrcOut(background, source, amount); } @@ -1078,17 +814,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightSrcOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightSrcOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightSrcOut(background, source, amount); } @@ -1102,17 +832,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalDest(background, source, amount); } @@ -1126,17 +850,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyDest(background, source, amount); } @@ -1150,17 +868,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddDest(background, source, amount); } @@ -1174,17 +886,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractDest(background, source, amount); } @@ -1198,17 +904,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenDest(background, source, amount); } @@ -1222,17 +922,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenDest(background, source, amount); } @@ -1246,17 +940,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenDest(background, source, amount); } @@ -1270,17 +958,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayDest(background, source, amount); } @@ -1294,17 +976,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightDest(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightDest(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightDest(background, source, amount); } @@ -1318,17 +994,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalDestAtop(background, source, amount); } @@ -1342,17 +1012,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); } @@ -1366,17 +1030,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddDestAtop(background, source, amount); } @@ -1390,17 +1048,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractDestAtop(background, source, amount); } @@ -1414,17 +1066,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenDestAtop(background, source, amount); } @@ -1438,17 +1084,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenDestAtop(background, source, amount); } @@ -1462,17 +1102,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenDestAtop(background, source, amount); } @@ -1486,17 +1120,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayDestAtop(background, source, amount); } @@ -1510,17 +1138,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightDestAtop(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightDestAtop(background, source, amount); } @@ -1534,17 +1156,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalDestOver(background, source, amount); } @@ -1558,17 +1174,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyDestOver(background, source, amount); } @@ -1582,17 +1192,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddDestOver(background, source, amount); } @@ -1606,17 +1210,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractDestOver(background, source, amount); } @@ -1630,17 +1228,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenDestOver(background, source, amount); } @@ -1654,17 +1246,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenDestOver(background, source, amount); } @@ -1678,17 +1264,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenDestOver(background, source, amount); } @@ -1702,17 +1282,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayDestOver(background, source, amount); } @@ -1726,17 +1300,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightDestOver(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightDestOver(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightDestOver(background, source, amount); } @@ -1750,17 +1318,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalDestIn(background, source, amount); } @@ -1774,17 +1336,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyDestIn(background, source, amount); } @@ -1798,17 +1354,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddDestIn(background, source, amount); } @@ -1822,17 +1372,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractDestIn(background, source, amount); } @@ -1846,17 +1390,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenDestIn(background, source, amount); } @@ -1870,17 +1408,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenDestIn(background, source, amount); } @@ -1894,17 +1426,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenDestIn(background, source, amount); } @@ -1918,17 +1444,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayDestIn(background, source, amount); } @@ -1942,17 +1462,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightDestIn(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightDestIn(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightDestIn(background, source, amount); } @@ -1966,17 +1480,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalDestOut(background, source, amount); } @@ -1990,17 +1498,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyDestOut(background, source, amount); } @@ -2014,17 +1516,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddDestOut(background, source, amount); } @@ -2038,17 +1534,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractDestOut(background, source, amount); } @@ -2062,17 +1552,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenDestOut(background, source, amount); } @@ -2086,17 +1570,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenDestOut(background, source, amount); } @@ -2110,17 +1588,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenDestOut(background, source, amount); } @@ -2134,17 +1606,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayDestOut(background, source, amount); } @@ -2158,17 +1624,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightDestOut(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightDestOut(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightDestOut(background, source, amount); } @@ -2182,17 +1642,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalClear(background, source, amount); } @@ -2206,17 +1660,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyClear(background, source, amount); } @@ -2230,17 +1678,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddClear(background, source, amount); } @@ -2254,17 +1696,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractClear(background, source, amount); } @@ -2278,17 +1714,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenClear(background, source, amount); } @@ -2302,17 +1732,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenClear(background, source, amount); } @@ -2326,17 +1750,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenClear(background, source, amount); } @@ -2350,17 +1768,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayClear(background, source, amount); } @@ -2374,17 +1786,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightClear(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightClear(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightClear(background, source, amount); } @@ -2398,17 +1804,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.NormalXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.NormalXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.NormalXor(background, source, amount); } @@ -2422,17 +1822,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.MultiplyXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.MultiplyXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.MultiplyXor(background, source, amount); } @@ -2446,17 +1840,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.AddXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.AddXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.AddXor(background, source, amount); } @@ -2470,17 +1858,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.SubtractXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.SubtractXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.SubtractXor(background, source, amount); } @@ -2494,17 +1876,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.ScreenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.ScreenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.ScreenXor(background, source, amount); } @@ -2518,17 +1894,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.DarkenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.DarkenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.DarkenXor(background, source, amount); } @@ -2542,17 +1912,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.LightenXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.LightenXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.LightenXor(background, source, amount); } @@ -2566,17 +1930,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.OverlayXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.OverlayXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.OverlayXor(background, source, amount); } @@ -2590,17 +1948,11 @@ internal static class DefaultPixelBlenderOperators => PorterDuffFunctions.HardLightXor(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.HardLightXor(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.HardLightXor(background, source, amount); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 99610b7d8..ae0e0700d 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -66,17 +66,11 @@ foreach (var composer in composers) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - public static Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount) + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - public static Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount) + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs index 902ba9e25..10b588666 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs @@ -27,10 +27,7 @@ internal interface IPixelBlenderOperator /// The source RGBA lanes. /// The source opacity repeated across each pixel's four lanes. /// The blended RGBA lanes. - public static abstract Vector256 Invoke( - Vector256 background, - Vector256 source, - Vector256 amount); + public static abstract Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount); /// /// Blends four pixels represented by four consecutive groups of four RGBA lanes. @@ -39,8 +36,5 @@ internal interface IPixelBlenderOperator /// The source RGBA lanes. /// The source opacity repeated across each pixel's four lanes. /// The blended RGBA lanes. - public static abstract Vector512 Invoke( - Vector512 background, - Vector512 source, - Vector512 amount); + public static abstract Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs index f50151256..17282e388 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs @@ -5,7 +5,6 @@ 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; @@ -19,11 +18,7 @@ internal abstract class PixelBlender : PixelBlender where TOperator : struct, IPixelBlenderOperator { /// - protected sealed override void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount) + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { // Public entry points validate the row lengths, so all three references can advance in lockstep. int scalarStart = 0; @@ -33,7 +28,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { // A 512-bit register holds four complete RGBA pixels in [R,G,B,A] groups. ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); @@ -44,15 +39,12 @@ internal abstract class PixelBlender : PixelBlender for (nuint i = 0; i < (uint)vectorCount; i++) { - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - Unsafe.Add(ref sourceBase, i), - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { // A 256-bit register holds two complete RGBA pixels in [R,G,B,A] groups. ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); @@ -63,10 +55,7 @@ internal abstract class PixelBlender : PixelBlender for (nuint i = 0; i < (uint)vectorCount; i++) { - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - Unsafe.Add(ref sourceBase, i), - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); } scalarStart = vectorCount * 2; @@ -75,19 +64,12 @@ internal abstract class PixelBlender : PixelBlender // Vector4 is both the scalar pixel representation and the portable SIMD fallback. for (int i = scalarStart; i < destination.Length; i++) { - Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( - Unsafe.Add(ref backgroundRef, (uint)i), - Unsafe.Add(ref sourceRef, (uint)i), - amount); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), amount); } } /// - protected sealed override void BlendFunction( - Span destination, - ReadOnlySpan background, - Vector4 source, - float amount) + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) { // Public entry points validate the row lengths, so the destination and background advance together. int scalarStart = 0; @@ -96,7 +78,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { // Repeat one [R,G,B,A] source group four times to match the four background pixels. ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); @@ -107,15 +89,12 @@ internal abstract class PixelBlender : PixelBlender for (nuint i = 0; i < (uint)vectorCount; i++) { - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - sourceVector, - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { // Repeat one [R,G,B,A] source group twice to match the two background pixels. ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); @@ -126,10 +105,7 @@ internal abstract class PixelBlender : PixelBlender for (nuint i = 0; i < (uint)vectorCount; i++) { - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - sourceVector, - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); } scalarStart = vectorCount * 2; @@ -138,19 +114,12 @@ internal abstract class PixelBlender : PixelBlender // The remaining pixel count is at most three after AVX-512 or one after AVX2. for (int i = scalarStart; i < destination.Length; i++) { - Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( - Unsafe.Add(ref backgroundRef, (uint)i), - source, - amount); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, amount); } } /// - protected sealed override void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) { // Each amount belongs to one pixel and must be repeated across that pixel's four RGBA lanes. int scalarStart = 0; @@ -160,7 +129,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); ref float amountRef = ref MemoryMarshal.GetReference(amount); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -173,15 +142,12 @@ internal abstract class PixelBlender : PixelBlender ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); Vector512 amountVector = CreateClampedVector512(ref amountBase); - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - Unsafe.Add(ref sourceBase, i), - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -194,10 +160,7 @@ internal abstract class PixelBlender : PixelBlender ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); Vector256 amountVector = CreateClampedVector256(ref amountBase); - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - Unsafe.Add(ref sourceBase, i), - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); } scalarStart = vectorCount * 2; @@ -205,19 +168,12 @@ internal abstract class PixelBlender : PixelBlender for (int i = scalarStart; i < destination.Length; i++) { - Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( - Unsafe.Add(ref backgroundRef, (uint)i), - Unsafe.Add(ref sourceRef, (uint)i), - Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); } } /// - protected sealed override void BlendFunction( - Span destination, - ReadOnlySpan background, - Vector4 source, - ReadOnlySpan amount) + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) { // The source is invariant, while each background pixel has its own independently clamped amount. int scalarStart = 0; @@ -226,7 +182,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); ref float amountRef = ref MemoryMarshal.GetReference(amount); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -238,15 +194,12 @@ internal abstract class PixelBlender : PixelBlender ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); Vector512 amountVector = CreateClampedVector512(ref amountBase); - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - sourceVector, - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -258,10 +211,7 @@ internal abstract class PixelBlender : PixelBlender ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); Vector256 amountVector = CreateClampedVector256(ref amountBase); - Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( - Unsafe.Add(ref backgroundBase, i), - sourceVector, - amountVector); + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); } scalarStart = vectorCount * 2; @@ -269,20 +219,12 @@ internal abstract class PixelBlender : PixelBlender for (int i = scalarStart; i < destination.Length; i++) { - Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( - Unsafe.Add(ref backgroundRef, (uint)i), - source, - Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); } } /// - protected sealed override void BlendWithCoverageFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount, - ReadOnlySpan coverage) + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) { // Coverage mixes the composed result back toward the original background, so it is fused into this pass. int scalarStart = 0; @@ -293,7 +235,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); ref float coverageRef = ref MemoryMarshal.GetReference(coverage); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -306,20 +248,14 @@ internal abstract class PixelBlender : PixelBlender ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); Vector512 coverageVector = CreateClampedVector512(ref coverageBase); - Vector512 blended = TOperator.Invoke( - backgroundVector, - Unsafe.Add(ref sourceBase, i), - amountVector); - - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Vector512 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -332,15 +268,9 @@ internal abstract class PixelBlender : PixelBlender ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); Vector256 coverageVector = CreateClampedVector256(ref coverageBase); - Vector256 blended = TOperator.Invoke( - backgroundVector, - Unsafe.Add(ref sourceBase, i), - amountVector); - - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Vector256 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 2; @@ -349,25 +279,14 @@ internal abstract class PixelBlender : PixelBlender for (int i = scalarStart; i < destination.Length; i++) { Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); - Vector4 blended = TOperator.Invoke( - backgroundPixel, - Unsafe.Add(ref sourceRef, (uint)i), - amount); - - Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( - backgroundPixel, - blended, - Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), amount); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); } } /// - protected sealed override void BlendWithCoverageFunction( - Span destination, - ReadOnlySpan background, - Vector4 source, - float amount, - ReadOnlySpan coverage) + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) { // The constant source is expanded once per selected width and reused for the complete row. int scalarStart = 0; @@ -377,7 +296,7 @@ internal abstract class PixelBlender : PixelBlender ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); ref float coverageRef = ref MemoryMarshal.GetReference(coverage); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -392,15 +311,12 @@ internal abstract class PixelBlender : PixelBlender Vector512 coverageVector = CreateClampedVector512(ref coverageBase); Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -415,10 +331,7 @@ internal abstract class PixelBlender : PixelBlender Vector256 coverageVector = CreateClampedVector256(ref coverageBase); Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 2; @@ -429,20 +342,12 @@ internal abstract class PixelBlender : PixelBlender Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); Vector4 blended = TOperator.Invoke(backgroundPixel, source, amount); - Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( - backgroundPixel, - blended, - Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); } } /// - protected sealed override void BlendWithCoverageFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount, - ReadOnlySpan coverage) + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) { // Amount controls composition while coverage controls the final mix with the untouched background. int scalarStart = 0; @@ -453,7 +358,7 @@ internal abstract class PixelBlender : PixelBlender ref float amountRef = ref MemoryMarshal.GetReference(amount); ref float coverageRef = ref MemoryMarshal.GetReference(coverage); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -467,20 +372,14 @@ internal abstract class PixelBlender : PixelBlender ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); Vector512 amountVector = CreateClampedVector512(ref amountBase); Vector512 coverageVector = CreateClampedVector512(ref coverageBase); - Vector512 blended = TOperator.Invoke( - backgroundVector, - Unsafe.Add(ref sourceBase, i), - amountVector); - - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Vector512 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -494,15 +393,9 @@ internal abstract class PixelBlender : PixelBlender ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); Vector256 amountVector = CreateClampedVector256(ref amountBase); Vector256 coverageVector = CreateClampedVector256(ref coverageBase); - Vector256 blended = TOperator.Invoke( - backgroundVector, - Unsafe.Add(ref sourceBase, i), - amountVector); - - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Vector256 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 2; @@ -511,25 +404,14 @@ internal abstract class PixelBlender : PixelBlender for (int i = scalarStart; i < destination.Length; i++) { Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); - Vector4 blended = TOperator.Invoke( - backgroundPixel, - Unsafe.Add(ref sourceRef, (uint)i), - Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); - - Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( - backgroundPixel, - blended, - Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); } } /// - protected sealed override void BlendWithCoverageFunction( - Span destination, - ReadOnlySpan background, - Vector4 source, - ReadOnlySpan amount, - ReadOnlySpan coverage) + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) { // The invariant source is expanded once; only amount and coverage are gathered for each vector batch. int scalarStart = 0; @@ -539,7 +421,7 @@ internal abstract class PixelBlender : PixelBlender ref float amountRef = ref MemoryMarshal.GetReference(amount); ref float coverageRef = ref MemoryMarshal.GetReference(coverage); - if (Avx512F.IsSupported && destination.Length >= 4) + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) { ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -555,15 +437,12 @@ internal abstract class PixelBlender : PixelBlender Vector512 coverageVector = CreateClampedVector512(ref coverageBase); Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 4; } - else if (Avx2.IsSupported && destination.Length >= 2) + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) { ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); @@ -579,10 +458,7 @@ internal abstract class PixelBlender : PixelBlender Vector256 coverageVector = CreateClampedVector256(ref coverageBase); Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); - Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( - backgroundVector, - blended, - coverageVector); + Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); } scalarStart = vectorCount * 2; @@ -591,15 +467,9 @@ internal abstract class PixelBlender : PixelBlender for (int i = scalarStart; i < destination.Length; i++) { Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); - Vector4 blended = TOperator.Invoke( - backgroundPixel, - source, - Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); - - Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( - backgroundPixel, - blended, - Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); + Vector4 blended = TOperator.Invoke(backgroundPixel, source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); } } @@ -619,23 +489,7 @@ internal abstract class PixelBlender : PixelBlender /// Four consecutive copies of the pixel. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector512 CreateVector512(Vector4 pixel) - => Vector512.Create( - pixel.X, - pixel.Y, - pixel.Z, - pixel.W, - pixel.X, - pixel.Y, - pixel.Z, - pixel.W, - pixel.X, - pixel.Y, - pixel.Z, - pixel.W, - pixel.X, - pixel.Y, - pixel.Z, - pixel.W); + => Vector512.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); /// /// Expands and clamps two per-pixel scalar values for two packed RGBA pixels. @@ -645,12 +499,10 @@ internal abstract class PixelBlender : PixelBlender [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector256 CreateClampedVector256(ref float values) { - Vector256 result = Vector256.Create( - Vector128.Create(values), - Vector128.Create(Unsafe.Add(ref values, 1))); + Vector256 result = Vector256.Create(Vector128.Create(values), Vector128.Create(Unsafe.Add(ref values, 1))); // Amount and coverage share the same public 0..1 contract and therefore the same packed clamp. - return Avx.Min(Avx.Max(Vector256.Zero, result), Vector256.Create(1F)); + return Vector256.Min(Vector256.Max(Vector256.Zero, result), Vector256.Create(1F)); } /// @@ -661,24 +513,9 @@ internal abstract class PixelBlender : PixelBlender [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector512 CreateClampedVector512(ref float values) { - Vector512 result = Vector512.Create( - values, - values, - values, - values, - Unsafe.Add(ref values, 1), - Unsafe.Add(ref values, 1), - Unsafe.Add(ref values, 1), - Unsafe.Add(ref values, 1), - Unsafe.Add(ref values, 2), - Unsafe.Add(ref values, 2), - Unsafe.Add(ref values, 2), - Unsafe.Add(ref values, 2), - Unsafe.Add(ref values, 3), - Unsafe.Add(ref values, 3), - Unsafe.Add(ref values, 3), - Unsafe.Add(ref values, 3)); + Vector512 result = Vector512.Create(values, values, values, values, Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3)); + // Amount and coverage share the same public 0..1 contract and therefore the same packed clamp. return Vector512.Min(Vector512.Max(Vector512.Zero, result), Vector512.Create(1F)); } } @@ -696,10 +533,7 @@ internal abstract class DefaultPixelBlender : PixelBlender vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); vector = transform.Invoke(vector); } @@ -66,8 +65,7 @@ internal static partial class Vector4Converters for (; index <= oneRegisterFromEnd; index += vectorsPerRegister) { - ref Vector256 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); vector = transform.Invoke(vector); } @@ -79,8 +77,7 @@ internal static partial class Vector4Converters // consumes every remaining complete pixel and leaves no scalar remainder. for (; index < vectors.Length; index++) { - ref Vector128 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); vector = transform.Invoke(vector); } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs index 0b08061bc..96d81617b 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs @@ -73,9 +73,7 @@ internal static partial class Vector4Converters [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 Invoke(Vector4 source) { - Vector128 result = - (source.AsVector128() * this.multiplier.GetLower().GetLower()) - + this.offset.GetLower().GetLower(); + Vector128 result = (source.AsVector128() * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); return result.AsVector4(); } @@ -126,9 +124,7 @@ internal static partial class Vector4Converters [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 Invoke(Vector4 source) { - Vector128 result = - (source.AsVector128() + this.offset.GetLower().GetLower()) - / this.divisor.GetLower().GetLower(); + Vector128 result = (source.AsVector128() + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); return result.AsVector4(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 9f9dfe433..ae4229807 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class CmykColorConversion : ColorConversionBenchmark { - private readonly JpegColorConverterBase converter = - JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); /// /// Initializes a new instance of the class. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 5776f4720..9e5bfed12 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class GrayScaleColorConversion : ColorConversionBenchmark { - private readonly JpegColorConverterBase converter = - JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); /// /// Initializes a new instance of the class. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs new file mode 100644 index 000000000..5c36ae59f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Compares the previous scalar JPEG packing loops with the SIMD register-transpose implementation. +/// +[Config(typeof(Config.Short))] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class JpegColorPacking +{ + private const float MaximumValue = 255F; + private const float Scale = 1F / MaximumValue; + + private float[] x = null!; + private float[] y = null!; + private float[] z = null!; + private float[] w = null!; + private Vector3[] packed3 = null!; + private float[] destination3 = null!; + private float[] destination4 = null!; + + /// + /// Gets or sets the number of pixels transformed by each benchmark invocation. + /// + [Params(128, 1024, 4096)] + public int Length { get; set; } + + /// + /// Creates deterministic source and destination buffers outside the measured operations. + /// + [GlobalSetup] + public void Setup() + { + this.x = CreateSamples(this.Length, 1); + this.y = CreateSamples(this.Length, 2); + this.z = CreateSamples(this.Length, 3); + this.w = CreateSamples(this.Length, 4); + this.packed3 = new Vector3[this.Length]; + this.destination3 = new float[this.Length * 3]; + this.destination4 = new float[this.Length * 4]; + + for (int i = 0; i < this.packed3.Length; i++) + { + this.packed3[i] = new Vector3(this.x[i], this.y[i], this.z[i]); + } + } + + /// + /// Measures the previous scalar three-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Pack3")] + public float PackedNormalizeInterleave3Scalar() + { + JpegColorPackingScalar.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); + + return this.destination3[^1]; + } + + /// + /// Measures the SIMD three-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("Pack3")] + public float PackedNormalizeInterleave3Simd() + { + JpegColorConverterBase.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); + + return this.destination3[^1]; + } + + /// + /// Measures the previous scalar packed-three-channel deinterleave loop. + /// + /// A checksum containing the last value written to every destination plane. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Unpack3")] + public float UnpackDeinterleave3Scalar() + { + JpegColorPackingScalar.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); + + return this.x[^1] + this.y[^1] + this.z[^1]; + } + + /// + /// Measures the SIMD packed-three-channel deinterleave implementation. + /// + /// A checksum containing the last value written to every destination plane. + [Benchmark] + [BenchmarkCategory("Unpack3")] + public float UnpackDeinterleave3Simd() + { + JpegColorConverterBase.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); + + return this.x[^1] + this.y[^1] + this.z[^1]; + } + + /// + /// Measures the previous scalar four-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Pack4")] + public float PackedNormalizeInterleave4Scalar() + { + JpegColorPackingScalar.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the SIMD four-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("Pack4")] + public float PackedNormalizeInterleave4Simd() + { + JpegColorConverterBase.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the previous scalar inverted four-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("InvertPack4")] + public float PackedInvertNormalizeInterleave4Scalar() + { + JpegColorPackingScalar.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the SIMD inverted four-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("InvertPack4")] + public float PackedInvertNormalizeInterleave4Simd() + { + JpegColorConverterBase.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Creates deterministic, non-integral samples for one component plane. + /// + /// The number of samples to create. + /// The one-based component number used to distinguish the plane. + /// The generated samples. + private static float[] CreateSamples(int length, int component) + { + float[] samples = new float[length]; + + for (int i = 0; i < samples.Length; i++) + { + samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); + } + + return samples; + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs new file mode 100644 index 000000000..511abc0db --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs @@ -0,0 +1,117 @@ +// 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.Benchmarks.Codecs.Jpeg; + +/// +/// Preserves the scalar JPEG packing loops that preceded the SIMD investigation. +/// +internal static class JpegColorPackingScalar +{ + /// + /// Normalizes and interleaves three planar component lanes using the previous scalar implementation. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The destination ordered as consecutive XYZ triples. + /// The normalization factor applied to every component. + public static void PackedNormalizeInterleave3(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, Span packed, float scale) + { + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 3; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; + } + } + + /// + /// Deinterleaves packed XYZ values using the previous scalar implementation. + /// + /// The source ordered as consecutive XYZ triples. + /// The destination X components. + /// The destination Y components. + /// The destination Z components. + public static void UnpackDeinterleave3(ReadOnlySpan packed, Span xLane, Span yLane, Span zLane) + { + ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + + for (nuint i = 0; i < (nuint)packed.Length; i++) + { + nuint packedOffset = i * 3; + Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); + Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); + Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); + } + } + + /// + /// Normalizes and interleaves four planar component lanes using the previous scalar implementation. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The planar W components. + /// The destination ordered as consecutive XYZW groups. + /// The maximum component value used to normalize each component. + public static void PackedNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maximumValue) + { + float scale = 1F / maximumValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 4; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, i) * scale; + } + } + + /// + /// Inverts, normalizes, and interleaves four planar lanes using the previous scalar implementation. + /// + /// The inverted planar X components. + /// The inverted planar Y components. + /// The inverted planar Z components. + /// The inverted planar W components. + /// The destination ordered as consecutive conventional XYZW groups. + /// The maximum component value used for inversion and normalization. + public static void PackedInvertNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maximumValue) + { + float scale = 1F / maximumValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 4; + Unsafe.Add(ref packedRef, packedOffset) = (maximumValue - Unsafe.Add(ref xLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = (maximumValue - Unsafe.Add(ref yLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = (maximumValue - Unsafe.Add(ref zLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = (maximumValue - Unsafe.Add(ref wLaneRef, i)) * scale; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index d72bbb40b..b1d72e3ca 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class RgbColorConversion : ColorConversionBenchmark { - private readonly JpegColorConverterBase converter = - JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); /// /// Initializes a new instance of the class. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 70936153a..1355aecd1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YCbCrColorConversion : ColorConversionBenchmark { - private readonly JpegColorConverterBase converter = - JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); /// /// Initializes a new instance of the class. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs deleted file mode 100644 index b67ee76df..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorAssembly.cs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Exposes every YCbCr operator overload directly to the disassembly diagnoser. -/// -[Config(typeof(Config.Analysis))] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class YCbCrOperatorAssembly -{ - private const float MaximumValue = 255F; - private const float HalfValue = 128F; - private const float Scale = 1F / MaximumValue; - - private float scalarC0 = 64F; - private float scalarC1 = 96F; - private float scalarC2 = 160F; - - private readonly Vector128 vector128C0 = Vector128.Create(64F); - private readonly Vector128 vector128C1 = Vector128.Create(96F); - private readonly Vector128 vector128C2 = Vector128.Create(160F); - private readonly Vector128 vector128Maximum = Vector128.Create(MaximumValue); - private readonly Vector128 vector128Half = Vector128.Create(HalfValue); - private readonly Vector128 vector128Scale = Vector128.Create(Scale); - - private readonly Vector256 vector256C0 = Vector256.Create(64F); - private readonly Vector256 vector256C1 = Vector256.Create(96F); - private readonly Vector256 vector256C2 = Vector256.Create(160F); - private readonly Vector256 vector256Maximum = Vector256.Create(MaximumValue); - private readonly Vector256 vector256Half = Vector256.Create(HalfValue); - private readonly Vector256 vector256Scale = Vector256.Create(Scale); - - private readonly Vector512 vector512C0 = Vector512.Create(64F); - private readonly Vector512 vector512C1 = Vector512.Create(96F); - private readonly Vector512 vector512C2 = Vector512.Create(160F); - private readonly Vector512 vector512Maximum = Vector512.Create(MaximumValue); - private readonly Vector512 vector512Half = Vector512.Create(HalfValue); - private readonly Vector512 vector512Scale = Vector512.Create(Scale); - - /// - /// Invokes the scalar JPEG-to-RGB operator. - /// - /// A checksum containing all three converted channels. - [Benchmark] - [BenchmarkCategory("ToRgb")] - public float ToRgbScalar() - { - float c0 = this.scalarC0; - float c1 = this.scalarC1; - float c2 = this.scalarC2; - - JpegColorConverterBase.YCbCrOperator.ConvertToRgb( - ref c0, - ref c1, - ref c2, - 0, - MaximumValue, - HalfValue, - Scale); - - // Returning the channel sum keeps every output live in the generated assembly. - return c0 + c1 + c2; - } - - /// - /// Invokes the Vector128 JPEG-to-RGB operator. - /// - /// A checksum containing all three converted channel vectors. - [Benchmark] - [BenchmarkCategory("ToRgb")] - public Vector128 ToRgbVector128() - { - Vector128 c0 = this.vector128C0; - Vector128 c1 = this.vector128C1; - Vector128 c2 = this.vector128C2; - - JpegColorConverterBase.YCbCrOperator.ConvertToRgb( - ref c0, - ref c1, - ref c2, - default, - this.vector128Maximum, - this.vector128Half, - this.vector128Scale); - - // The vector sum makes all RGB results observable without adding stores to the measured body. - return c0 + c1 + c2; - } - - /// - /// Invokes the Vector256 JPEG-to-RGB operator. - /// - /// A checksum containing all three converted channel vectors. - [Benchmark] - [BenchmarkCategory("ToRgb")] - public Vector256 ToRgbVector256() - { - Vector256 c0 = this.vector256C0; - Vector256 c1 = this.vector256C1; - Vector256 c2 = this.vector256C2; - - JpegColorConverterBase.YCbCrOperator.ConvertToRgb( - ref c0, - ref c1, - ref c2, - default, - this.vector256Maximum, - this.vector256Half, - this.vector256Scale); - - // The vector sum makes all RGB results observable without adding stores to the measured body. - return c0 + c1 + c2; - } - - /// - /// Invokes the Vector512 JPEG-to-RGB operator. - /// - /// A checksum containing all three converted channel vectors. - [Benchmark] - [BenchmarkCategory("ToRgb")] - public Vector512 ToRgbVector512() - { - Vector512 c0 = this.vector512C0; - Vector512 c1 = this.vector512C1; - Vector512 c2 = this.vector512C2; - - JpegColorConverterBase.YCbCrOperator.ConvertToRgb( - ref c0, - ref c1, - ref c2, - default, - this.vector512Maximum, - this.vector512Half, - this.vector512Scale); - - // The vector sum makes all RGB results observable without adding stores to the measured body. - return c0 + c1 + c2; - } - - /// - /// Invokes the scalar RGB-to-JPEG operator. - /// - /// A checksum containing all converted components. - [Benchmark] - [BenchmarkCategory("FromRgb")] - public float FromRgbScalar() - { - JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( - this.scalarC0, - this.scalarC1, - this.scalarC2, - MaximumValue, - HalfValue, - Scale, - out float c0, - out float c1, - out float c2, - out float c3); - - // c3 is deliberately included so a future four-component implementation remains observable. - return c0 + c1 + c2 + c3; - } - - /// - /// Invokes the Vector128 RGB-to-JPEG operator. - /// - /// A checksum containing all converted component vectors. - [Benchmark] - [BenchmarkCategory("FromRgb")] - public Vector128 FromRgbVector128() - { - JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( - this.vector128C0, - this.vector128C1, - this.vector128C2, - this.vector128Maximum, - this.vector128Half, - this.vector128Scale, - out Vector128 c0, - out Vector128 c1, - out Vector128 c2, - out Vector128 c3); - - // Include all planar results in the returned vector so the JIT retains every calculation. - return c0 + c1 + c2 + c3; - } - - /// - /// Invokes the Vector256 RGB-to-JPEG operator. - /// - /// A checksum containing all converted component vectors. - [Benchmark] - [BenchmarkCategory("FromRgb")] - public Vector256 FromRgbVector256() - { - JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( - this.vector256C0, - this.vector256C1, - this.vector256C2, - this.vector256Maximum, - this.vector256Half, - this.vector256Scale, - out Vector256 c0, - out Vector256 c1, - out Vector256 c2, - out Vector256 c3); - - // Include all planar results in the returned vector so the JIT retains every calculation. - return c0 + c1 + c2 + c3; - } - - /// - /// Invokes the Vector512 RGB-to-JPEG operator. - /// - /// A checksum containing all converted component vectors. - [Benchmark] - [BenchmarkCategory("FromRgb")] - public Vector512 FromRgbVector512() - { - JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( - this.vector512C0, - this.vector512C1, - this.vector512C2, - this.vector512Maximum, - this.vector512Half, - this.vector512Scale, - out Vector512 c0, - out Vector512 c1, - out Vector512 c2, - out Vector512 c3); - - // Include all planar results in the returned vector so the JIT retains every calculation. - return c0 + c1 + c2 + c3; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 80adff5cc..261711d0b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YccKColorConverter : ColorConversionBenchmark { - private readonly JpegColorConverterBase converter = - JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); /// /// Initializes a new instance of the class. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs deleted file mode 100644 index 5461cb149..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Png.Filters; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; - -/// -/// Exposes every normalized PNG filter for assembly inspection. -/// -[Config(typeof(Config.Analysis))] -public class PngFilterEncodeAssembly -{ - private const int BytesPerPixel = 4; - private const int Count = 180; - - private byte[] scanline; - private byte[] previousScanline; - private byte[] result; - - /// - /// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths. - /// - [GlobalSetup] - public void Setup() - { - this.scanline = new byte[Count]; - this.previousScanline = new byte[Count]; - this.result = new byte[Count + 1]; - - Random random = new(12345678); - random.NextBytes(this.scanline); - random.NextBytes(this.previousScanline); - } - - /// - /// Executes the normalized Sub encoder. - /// - [Benchmark] - public void Sub() - => SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out _); - - /// - /// Executes the normalized Up encoder. - /// - [Benchmark] - public void Up() - => UpFilter.Encode(this.scanline, this.previousScanline, this.result, out _); - - /// - /// Executes the normalized Average encoder. - /// - [Benchmark] - public void Average() - => AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); - - /// - /// Executes the normalized Paeth encoder. - /// - [Benchmark] - public void Paeth() - => PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs deleted file mode 100644 index 855d5644c..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -/// -/// Exposes every floating-point tensor compatibility operation for assembly inspection. -/// -[Config(typeof(Config.Analysis))] -public class TensorPrimitivesAssembly -{ - private const int Count = 2048; - - private readonly float[] x = new float[Count]; - private readonly float[] y = new float[Count]; - private readonly float[] destination = new float[Count]; - - /// - /// Populates the input spans with deterministic non-uniform values. - /// - [GlobalSetup] - public void Setup() - { - for (int i = 0; i < Count; i++) - { - this.x[i] = ((i * 17) % 251) + 1; - this.y[i] = ((i * 29) % 251) + 1; - } - } - - /// - /// Adds two floating-point spans. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public float Add() - { - TensorPrimitives_.Add(this.x, this.y, this.destination); - return this.destination[0]; - } - - /// - /// Clamps a floating-point span between scalar bounds. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public float Clamp() - { - TensorPrimitives_.Clamp(this.x, 64F, 128F, this.destination); - return this.destination[0]; - } - - /// - /// Divides a floating-point span by a scalar. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public float Divide() - { - TensorPrimitives_.Divide(this.x, 4096F, this.destination); - return this.destination[0]; - } - - /// - /// Computes the element-wise maximum of a floating-point span and a scalar. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public float Max() - { - TensorPrimitives_.Max(this.x, 64F, this.destination); - return this.destination[0]; - } - - /// - /// Multiplies a floating-point span by a scalar. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public float Multiply() - { - TensorPrimitives_.Multiply(this.x, 0.5F, this.destination); - return this.destination[0]; - } -} - -/// -/// Exposes integral addition specializations for assembly inspection. -/// -/// The integral element type. -[Config(typeof(Config.Analysis))] -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -public class TensorPrimitivesIntegralAddAssembly - where T : unmanaged, INumber -{ - private const int Count = 2048; - - private readonly T[] x = new T[Count]; - private readonly T[] y = new T[Count]; - private readonly T[] destination = new T[Count]; - - /// - /// Populates the input spans with deterministic non-uniform values. - /// - [GlobalSetup] - public void Setup() - { - for (int i = 0; i < Count; i++) - { - this.x[i] = T.CreateTruncating((i * 17) + 31); - this.y[i] = T.CreateTruncating((i * 29) + 7); - } - } - - /// - /// Adds two integral spans. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public T Add() - { - TensorPrimitives_.Add(this.x, this.y, this.destination); - return this.destination[0]; - } -} - -/// -/// Exposes integral clamp specializations for assembly inspection. -/// -/// The integral element type. -[Config(typeof(Config.Analysis))] -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(int))] -public class TensorPrimitivesIntegralClampAssembly - where T : unmanaged, INumber -{ - private const int Count = 2048; - - private readonly T[] source = new T[Count]; - private readonly T[] destination = new T[Count]; - private T min; - private T max; - - /// - /// Populates the input span and scalar bounds with deterministic values. - /// - [GlobalSetup] - public void Setup() - { - this.min = T.CreateTruncating(64); - this.max = T.CreateTruncating(128); - - for (int i = 0; i < Count; i++) - { - this.source[i] = T.CreateTruncating((i * 31) % 257); - } - } - - /// - /// Clamps an integral span between scalar bounds. - /// - /// The first result, which keeps the destination observable. - [Benchmark] - public T Clamp() - { - TensorPrimitives_.Clamp(this.source, this.min, this.max, this.destination); - return this.destination[0]; - } -} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs deleted file mode 100644 index b93e40e19..000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversionAssembly.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -/// -/// Exposes every stateless packed-pixel shuffle operator for assembly inspection. -/// -/// -/// Seven pixels expose the short-input and vector-tail paths. Seventeen pixels execute one -/// full intrinsic group and leave one complete pixel for the scalar remainder, making every -/// part of each generated traversal observable. -/// -[Config(typeof(Config.Analysis))] -public class PackedPixelConversionAssembly -{ - private byte[] source3; - private byte[] source4; - private byte[] destination3; - private byte[] destination4; - - /// - /// Gets or sets the number of pixels converted by each invocation. - /// - [Params(7, 17)] - public int Count { get; set; } - - /// - /// Populates the source buffers with deterministic non-uniform channel values. - /// - [GlobalSetup] - public void Setup() - { - this.source3 = new byte[this.Count * 3]; - this.source4 = new byte[this.Count * 4]; - this.destination3 = new byte[this.Count * 3]; - this.destination4 = new byte[this.Count * 4]; - - new Random(42).NextBytes(this.source3); - new Random(42).NextBytes(this.source4); - } - - /// - /// Executes the WXYZ four-to-four operator. - /// - [Benchmark] - public void Shuffle4Wxyz() => SimdUtils.Shuffle4(this.source4, this.destination4); - - /// - /// Executes the WZYX four-to-four operator. - /// - [Benchmark] - public void Shuffle4Wzyx() => SimdUtils.Shuffle4(this.source4, this.destination4); - - /// - /// Executes the YZWX four-to-four operator. - /// - [Benchmark] - public void Shuffle4Yzwx() => SimdUtils.Shuffle4(this.source4, this.destination4); - - /// - /// Executes the ZYXW four-to-four operator. - /// - [Benchmark] - public void Shuffle4Zyxw() => SimdUtils.Shuffle4(this.source4, this.destination4); - - /// - /// Executes the XWZY four-to-four operator. - /// - [Benchmark] - public void Shuffle4Xwzy() => SimdUtils.Shuffle4(this.source4, this.destination4); - - /// - /// Executes the XYZ four-to-three operator. - /// - [Benchmark] - public void Slice3Xyz() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); - - /// - /// Executes the YZW four-to-three operator. - /// - [Benchmark] - public void Slice3Yzw() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); - - /// - /// Executes the WZY four-to-three operator. - /// - [Benchmark] - public void Slice3Wzy() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); - - /// - /// Executes the ZYX four-to-three operator. - /// - [Benchmark] - public void Slice3Zyx() => SimdUtils.Shuffle4Slice3(this.source4, this.destination3); - - /// - /// Executes the XYZW three-to-four operator. - /// - [Benchmark] - public void Pad4Xyzw() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); - - /// - /// Executes the WXYZ three-to-four operator. - /// - [Benchmark] - public void Pad4Wxyz() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); - - /// - /// Executes the WZYX three-to-four operator. - /// - [Benchmark] - public void Pad4Wzyx() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); - - /// - /// Executes the ZYXW three-to-four operator. - /// - [Benchmark] - public void Pad4Zyxw() => SimdUtils.Pad3Shuffle4(this.source3, this.destination4); - - /// - /// Executes the ZYX three-to-three operator. - /// - [Benchmark] - public void Shuffle3Zyx() => SimdUtils.Shuffle3(this.source3, this.destination3); -} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs deleted file mode 100644 index 1b5767ebd..000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats.Utils; - -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; - -/// -/// Exposes every stateful affine operator and traversal remainder for assembly inspection. -/// -[Config(typeof(Config.Analysis))] -public class Vector4AffineTransformAssembly -{ - private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); - private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); - private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); - - private Vector4[] vectors; - - /// - /// Gets or sets the number of vectors transformed by each invocation. - /// - /// - /// Three vectors exercise the 256- and 128-bit stages. Seventeen vectors exercise - /// the 512-bit loop and leave one vector for the 128-bit remainder. - /// - [Params(3, 17)] - public int Count { get; set; } - - /// - /// Creates a non-uniform input buffer. - /// - [GlobalSetup] - public void Setup() - { - this.vectors = new Vector4[this.Count]; - - for (int i = 0; i < this.vectors.Length; i++) - { - this.vectors[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); - } - } - - /// - /// Executes the multiply-then-add stateful operator. - /// - [Benchmark] - public void MultiplyThenAdd() - => Vector4Converters.MultiplyThenAdd(this.vectors, Multiplier, Offset); - - /// - /// Executes the add-then-divide stateful operator. - /// - [Benchmark] - public void AddThenDivide() - => Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor); -} diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs deleted file mode 100644 index a8d6095c7..000000000 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; - -namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; - -/// -/// Exposes every shared pixel-blender traversal shape for assembly inspection. -/// -/// -/// Seven pixels leave three scalar pixels after AVX-512 or one scalar pixel after AVX2, so each -/// hardware job contains both its widest supported loop and the portable Vector4 remainder. -/// -[Config(typeof(Config.Analysis))] -public class PixelBlenderTraversalAssembly -{ - private const int Count = 7; - private const float Amount = .625F; - - private readonly ExposedNormalSrcOverBlender blender = new(); - private readonly Vector4[] destination = new Vector4[Count]; - private readonly Vector4[] background = new Vector4[Count]; - private readonly Vector4[] source = new Vector4[Count]; - private readonly float[] amounts = new float[Count]; - private readonly float[] coverage = new float[Count]; - private Vector4 constantSource; - - /// - /// Populates all lanes with deterministic, non-constant values. - /// - [GlobalSetup] - public void Setup() - { - Random random = new(42); - - for (int i = 0; i < Count; i++) - { - // Distinct RGBA lanes make incorrect pixel grouping visible in both results and assembly. - this.background[i] = CreatePixel(random); - this.source[i] = CreatePixel(random); - this.amounts[i] = random.NextSingle(); - this.coverage[i] = random.NextSingle(); - } - - this.constantSource = CreatePixel(random); - } - - /// - /// Blends a source row with one shared amount. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 SourceSpanScalarAmount() - { - this.blender.BlendSourceSpanScalarAmount( - this.destination, - this.background, - this.source, - Amount); - - return this.destination[^1]; - } - - /// - /// Blends a constant source with one shared amount. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 ConstantSourceScalarAmount() - { - this.blender.BlendConstantSourceScalarAmount( - this.destination, - this.background, - this.constantSource, - Amount); - - return this.destination[^1]; - } - - /// - /// Blends a source row with per-pixel amounts. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 SourceSpanAmountSpan() - { - this.blender.BlendSourceSpanAmountSpan( - this.destination, - this.background, - this.source, - this.amounts); - - return this.destination[^1]; - } - - /// - /// Blends a constant source with per-pixel amounts. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 ConstantSourceAmountSpan() - { - this.blender.BlendConstantSourceAmountSpan( - this.destination, - this.background, - this.constantSource, - this.amounts); - - return this.destination[^1]; - } - - /// - /// Blends a source row with one shared amount and per-pixel coverage. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 SourceSpanScalarAmountCoverage() - { - this.blender.BlendSourceSpanScalarAmountCoverage( - this.destination, - this.background, - this.source, - Amount, - this.coverage); - - return this.destination[^1]; - } - - /// - /// Blends a constant source with one shared amount and per-pixel coverage. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 ConstantSourceScalarAmountCoverage() - { - this.blender.BlendConstantSourceScalarAmountCoverage( - this.destination, - this.background, - this.constantSource, - Amount, - this.coverage); - - return this.destination[^1]; - } - - /// - /// Blends a source row with per-pixel amounts and coverage. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 SourceSpanAmountSpanCoverage() - { - this.blender.BlendSourceSpanAmountSpanCoverage( - this.destination, - this.background, - this.source, - this.amounts, - this.coverage); - - return this.destination[^1]; - } - - /// - /// Blends a constant source with per-pixel amounts and coverage. - /// - /// The final destination pixel. - [Benchmark] - public Vector4 ConstantSourceAmountSpanCoverage() - { - this.blender.BlendConstantSourceAmountSpanCoverage( - this.destination, - this.background, - this.constantSource, - this.amounts, - this.coverage); - - return this.destination[^1]; - } - - /// - /// Creates one non-constant RGBA sample. - /// - /// The deterministic value source. - /// The sample pixel. - private static Vector4 CreatePixel(Random random) - => new(random.NextSingle(), random.NextSingle(), random.NextSingle(), random.NextSingle()); - - /// - /// Exposes the protected shared traversal overloads without adding benchmark hooks to production APIs. - /// - private sealed class ExposedNormalSrcOverBlender : - DefaultPixelBlender - { - /// - /// Invokes the source-span, scalar-amount traversal. - /// - /// The destination vectors. - /// The background vectors. - /// The source vectors. - /// The shared source amount. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendSourceSpanScalarAmount( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount) - => this.BlendFunction(destination, background, source, amount); - - /// - /// Invokes the constant-source, scalar-amount traversal. - /// - /// The destination vectors. - /// The background vectors. - /// The constant source vector. - /// The shared source amount. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendConstantSourceScalarAmount( - Span destination, - ReadOnlySpan background, - Vector4 source, - float amount) - => this.BlendFunction(destination, background, source, amount); - - /// - /// Invokes the source-span, amount-span traversal. - /// - /// The destination vectors. - /// The background vectors. - /// The source vectors. - /// The per-pixel source amounts. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendSourceSpanAmountSpan( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - => this.BlendFunction(destination, background, source, amount); - - /// - /// Invokes the constant-source, amount-span traversal. - /// - /// The destination vectors. - /// The background vectors. - /// The constant source vector. - /// The per-pixel source amounts. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendConstantSourceAmountSpan( - Span destination, - ReadOnlySpan background, - Vector4 source, - ReadOnlySpan amount) - => this.BlendFunction(destination, background, source, amount); - - /// - /// Invokes the source-span, scalar-amount traversal with coverage. - /// - /// The destination vectors. - /// The background vectors. - /// The source vectors. - /// The shared source amount. - /// The per-pixel coverage values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendSourceSpanScalarAmountCoverage( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount, - ReadOnlySpan coverage) - => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); - - /// - /// Invokes the constant-source, scalar-amount traversal with coverage. - /// - /// The destination vectors. - /// The background vectors. - /// The constant source vector. - /// The shared source amount. - /// The per-pixel coverage values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendConstantSourceScalarAmountCoverage( - Span destination, - ReadOnlySpan background, - Vector4 source, - float amount, - ReadOnlySpan coverage) - => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); - - /// - /// Invokes the source-span, amount-span traversal with coverage. - /// - /// The destination vectors. - /// The background vectors. - /// The source vectors. - /// The per-pixel source amounts. - /// The per-pixel coverage values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendSourceSpanAmountSpanCoverage( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount, - ReadOnlySpan coverage) - => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); - - /// - /// Invokes the constant-source, amount-span traversal with coverage. - /// - /// The destination vectors. - /// The background vectors. - /// The constant source vector. - /// The per-pixel source amounts. - /// The per-pixel coverage values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void BlendConstantSourceAmountSpanCoverage( - Span destination, - ReadOnlySpan background, - Vector4 source, - ReadOnlySpan amount, - ReadOnlySpan coverage) - => this.BlendWithCoverageFunction(destination, background, source, amount, coverage); - } -} diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index 4bf1a0b66..cbe2e4066 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -303,30 +303,15 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle2103); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle2103); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle0123); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle0321); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0321); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3012); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle1230); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle1230); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -343,10 +328,7 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3012); + TestShuffleByte3Channel(size, (s, d) => SimdUtils.Shuffle3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -363,25 +345,13 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3210); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle2103); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle2103); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle0123); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3012); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -398,25 +368,13 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3210); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle0321); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0321); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle0123); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), - SimdUtils.Shuffle.MMShuffle3012); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index dae2d3c3f..76cd2a794 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -17,8 +17,7 @@ public class JpegColorConverterTests private const float FromRgbTolerance = 0.01F; // Independent model checks compare normalized colors at one tenth of a byte-domain sample. - private static readonly ApproximateColorProfileComparer ColorSpaceComparer = - new(epsilon: ColorProfileTolerance); + private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: ColorProfileTolerance); /// /// Verifies that unsupported color spaces are rejected by the converter factory. @@ -77,27 +76,13 @@ public class JpegColorConverterTests /// The JPEG color space. /// The expected closed converter type. [Theory] - [InlineData( - JpegColorSpace.Grayscale, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.RGB, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.Cmyk, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.YCbCr, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.Ycck, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.TiffCmyk, - typeof(JpegColorConverterBase.JpegColorConverter))] - [InlineData( - JpegColorSpace.TiffYccK, - typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.Grayscale, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.RGB, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.Cmyk, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.YCbCr, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.Ycck, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.TiffCmyk, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.TiffYccK, typeof(JpegColorConverterBase.JpegColorConverter))] internal void GetConverterReturnsClosedOperatorConverter(JpegColorSpace colorSpace, Type expectedType) { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); @@ -152,10 +137,7 @@ public class JpegColorConverterTests [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] [InlineData(JpegColorSpace.TiffYccK, 4, 8)] [InlineData(JpegColorSpace.TiffYccK, 4, 12)] - internal void OperatorTraversalMatchesScalarDefinition( - JpegColorSpace colorSpace, - int componentCount, - int precision) + internal void OperatorTraversalMatchesScalarDefinition(JpegColorSpace colorSpace, int componentCount, int precision) { switch (colorSpace) { @@ -191,9 +173,7 @@ public class JpegColorConverterTests /// [Fact] public void OperatorTraversalMatchesScalarWithoutHardwareIntrinsics() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunWithoutHardwareIntrinsics, - HwIntrinsics.DisableHWIntrinsic); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunWithoutHardwareIntrinsics, HwIntrinsics.DisableHWIntrinsic); /// /// Verifies TIFF YccK encoding against the canonical normalized color-profile conversion. @@ -264,8 +244,7 @@ public class JpegColorConverterTests private static void ValidateOperator(int componentCount, int precision) where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - JpegColorConverterBase converter = - new JpegColorConverterBase.JpegColorConverter(precision); + JpegColorConverterBase converter = new JpegColorConverterBase.JpegColorConverter(precision); int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; // Adjacent values around 4/8/16 lanes verify every prefix, mixed-width tail, and scalar remainder. @@ -284,11 +263,7 @@ public class JpegColorConverterTests /// The number of samples to convert. /// The number of source component planes. /// The JPEG sample precision. - private static void ValidateConversionToRgb( - JpegColorConverterBase converter, - int length, - int componentCount, - int precision) + private static void ValidateConversionToRgb(JpegColorConverterBase converter, int length, int componentCount, int precision) where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); @@ -326,11 +301,7 @@ public class JpegColorConverterTests /// The number of samples to convert. /// The number of destination component planes. /// The JPEG sample precision. - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - int length, - int componentCount, - int precision) + private static void ValidateConversionFromRgb(JpegColorConverterBase converter, int length, int componentCount, int precision) where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); @@ -345,17 +316,7 @@ public class JpegColorConverterTests for (int i = 0; i < length; i++) { - TOperator.ConvertFromRgb( - r[i], - g[i], - b[i], - maximumValue, - halfValue, - scale, - out expected.Component0[i], - out float c1, - out float c2, - out float c3); + TOperator.ConvertFromRgb(r[i], g[i], b[i], maximumValue, halfValue, scale, out expected.Component0[i], out float c1, out float c2, out float c3); if (componentCount >= 2) { @@ -399,10 +360,7 @@ public class JpegColorConverterTests /// The number of independent component planes. /// The JPEG sample precision and deterministic random seed. /// The generated component planes. - private static JpegColorConverterBase.ComponentValues CreateRandomValues( - int length, - int componentCount, - int precision) + private static JpegColorConverterBase.ComponentValues CreateRandomValues(int length, int componentCount, int precision) { Random random = new(precision); float maximumValue = MathF.Pow(2, precision) - 1; @@ -449,11 +407,7 @@ public class JpegColorConverterTests /// The unmodified source component planes. /// The converted RGB planes. /// The sample index. - private static void AssertColorModelDefinition( - JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues source, - in JpegColorConverterBase.ComponentValues actual, - int index) + private static void AssertColorModelDefinition(JpegColorSpace colorSpace, in JpegColorConverterBase.ComponentValues source, in JpegColorConverterBase.ComponentValues actual, int index) { float c0 = source.Component0[index]; float c1 = source.Component1[index]; @@ -468,18 +422,12 @@ public class JpegColorConverterTests expected = new Rgb(luminance, luminance, luminance); break; case JpegColorSpace.RGB: - expected = new Rgb( - c0 / MaxColorChannelValue, - c1 / MaxColorChannelValue, - c2 / MaxColorChannelValue); + expected = new Rgb(c0 / MaxColorChannelValue, c1 / MaxColorChannelValue, c2 / MaxColorChannelValue); break; case JpegColorSpace.Cmyk: c3 = source.Component3[index] / MaxColorChannelValue; - expected = new Rgb( - c0 * c3 / MaxColorChannelValue, - c1 * c3 / MaxColorChannelValue, - c2 * c3 / MaxColorChannelValue); + expected = new Rgb(c0 * c3 / MaxColorChannelValue, c1 * c3 / MaxColorChannelValue, c2 * c3 / MaxColorChannelValue); break; case JpegColorSpace.YCbCr: @@ -487,10 +435,7 @@ public class JpegColorConverterTests c2 -= 128F; // JPEG applies the BT.601 matrix in the integer sample domain and rounds before normalization. - expected = new Rgb( - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero) / MaxColorChannelValue); + expected = new Rgb(MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero) / MaxColorChannelValue); break; case JpegColorSpace.Ycck: @@ -499,10 +444,7 @@ public class JpegColorConverterTests c3 = source.Component3[index] / MaxColorChannelValue; // Adobe YccK reconstructs inverted RGB first, then applies the normalized black component. - expected = new Rgb( - (MaxColorChannelValue - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, - (MaxColorChannelValue - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, - (MaxColorChannelValue - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue); + expected = new Rgb((MaxColorChannelValue - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, (MaxColorChannelValue - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, (MaxColorChannelValue - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue); break; default: @@ -513,12 +455,9 @@ public class JpegColorConverterTests // Color-space comparison intentionally clamps both sides because JPEG reconstruction can overshoot // the normalized RGB gamut and saturation belongs to the eventual pixel conversion. Rgb clampedExpected = Rgb.Clamp(expected); - Rgb clampedActual = Rgb.Clamp( - new Rgb(actual.Component0[index], actual.Component1[index], actual.Component2[index])); + Rgb clampedActual = Rgb.Clamp(new Rgb(actual.Component0[index], actual.Component1[index], actual.Component2[index])); - Assert.True( - ColorSpaceComparer.Equals(clampedExpected, clampedActual), - $"Colors {clampedExpected} and {clampedActual} are not equal at index {index}."); + Assert.True(ColorSpaceComparer.Equals(clampedExpected, clampedActual), $"Colors {clampedExpected} and {clampedActual} are not equal at index {index}."); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs new file mode 100644 index 000000000..20b4f1679 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +/// +/// Tests the planar and packed buffer transformations used around JPEG color-profile conversion. +/// +[Trait("Format", "Jpg")] +public class JpegColorPackingTests +{ + private static readonly int[] Lengths = [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 129]; + + /// + /// Verifies every packing operation against its scalar definition with and without hardware intrinsics. + /// + [Fact] + public void PackingOperationsMatchScalarDefinitions() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePackingOperations, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Exercises every SIMD transition and scalar remainder for the packing operations. + /// + private static void ValidatePackingOperations() + { + foreach (int length in Lengths) + { + ValidatePackedNormalizeInterleave3(length); + ValidateUnpackDeinterleave3(length); + ValidatePackedNormalizeInterleave4(length); + ValidatePackedInvertNormalizeInterleave4(length); + } + } + + /// + /// Compares normalized three-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedNormalizeInterleave3(int length) + { + const float scale = 1F / 255F; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] expected = new float[length * 3]; + float[] actual = new float[length * 3]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 3; + expected[packedOffset] = x[i] * scale; + expected[packedOffset + 1] = y[i] * scale; + expected[packedOffset + 2] = z[i] * scale; + } + + JpegColorConverterBase.PackedNormalizeInterleave3(x, y, z, actual, scale); + + Assert.Equal(expected, actual); + } + + /// + /// Compares packed three-channel deinterleaving with the original scalar loop. + /// + /// The number of packed values. + private static void ValidateUnpackDeinterleave3(int length) + { + Vector3[] packed = new Vector3[length]; + float[] expectedX = CreateSamples(length, 1); + float[] expectedY = CreateSamples(length, 2); + float[] expectedZ = CreateSamples(length, 3); + float[] actualX = new float[length]; + float[] actualY = new float[length]; + float[] actualZ = new float[length]; + + for (int i = 0; i < length; i++) + { + packed[i] = new Vector3(expectedX[i], expectedY[i], expectedZ[i]); + } + + JpegColorConverterBase.UnpackDeinterleave3(packed, actualX, actualY, actualZ); + + Assert.Equal(expectedX, actualX); + Assert.Equal(expectedY, actualY); + Assert.Equal(expectedZ, actualZ); + } + + /// + /// Compares normalized four-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedNormalizeInterleave4(int length) + { + const float maximumValue = 255F; + const float scale = 1F / maximumValue; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] w = CreateSamples(length, 4); + float[] expected = new float[length * 4]; + float[] actual = new float[length * 4]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 4; + expected[packedOffset] = x[i] * scale; + expected[packedOffset + 1] = y[i] * scale; + expected[packedOffset + 2] = z[i] * scale; + expected[packedOffset + 3] = w[i] * scale; + } + + JpegColorConverterBase.PackedNormalizeInterleave4(x, y, z, w, actual, maximumValue); + + Assert.Equal(expected, actual); + } + + /// + /// Compares inverted normalized four-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedInvertNormalizeInterleave4(int length) + { + const float maximumValue = 255F; + const float scale = 1F / maximumValue; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] w = CreateSamples(length, 4); + float[] expected = new float[length * 4]; + float[] actual = new float[length * 4]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 4; + expected[packedOffset] = (maximumValue - x[i]) * scale; + expected[packedOffset + 1] = (maximumValue - y[i]) * scale; + expected[packedOffset + 2] = (maximumValue - z[i]) * scale; + expected[packedOffset + 3] = (maximumValue - w[i]) * scale; + } + + JpegColorConverterBase.PackedInvertNormalizeInterleave4(x, y, z, w, actual, maximumValue); + + Assert.Equal(expected, actual); + } + + /// + /// Creates deterministic, non-integral sample values that expose lane-order and arithmetic mistakes. + /// + /// The number of samples to create. + /// The one-based component number used to distinguish each plane. + /// The generated sample values. + private static float[] CreateSamples(int length, int component) + { + float[] samples = new float[length]; + + for (int i = 0; i < samples.Length; i++) + { + // The relatively prime multipliers produce a distinct sequence for each plane + // while keeping every value inside the eight-bit JPEG sample domain. + samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); + } + + return samples; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs index 0e76f4fe9..423d54e5f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs @@ -63,9 +63,7 @@ public class PngEncoderFilterTests : MeasureFixture data.TestFilter(); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } [Fact] @@ -213,12 +211,7 @@ public class PngEncoderFilterTests : MeasureFixture /// [Fact] public void EncodeMatchesReferencesAcrossRegisterBoundaries() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertEncodersMatchReferences, - HwIntrinsics.AllowAll - | HwIntrinsics.DisableAVX512F - | HwIntrinsics.DisableAVX2 - | HwIntrinsics.DisableHWIntrinsic); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertEncodersMatchReferences, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); /// /// Compares every filter with its independent scalar reference across SIMD boundaries and pixel strides. @@ -243,29 +236,13 @@ public class PngEncoderFilterTests : MeasureFixture random.NextBytes(scanline); random.NextBytes(previousScanline); - AssertFilterMatchesReference( - PngFilterMethod.Sub, - scanline, - previousScanline, - bytesPerPixel); - - AssertFilterMatchesReference( - PngFilterMethod.Up, - scanline, - previousScanline, - bytesPerPixel); - - AssertFilterMatchesReference( - PngFilterMethod.Average, - scanline, - previousScanline, - bytesPerPixel); - - AssertFilterMatchesReference( - PngFilterMethod.Paeth, - scanline, - previousScanline, - bytesPerPixel); + AssertFilterMatchesReference(PngFilterMethod.Sub, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Up, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Average, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Paeth, scanline, previousScanline, bytesPerPixel); } } } @@ -277,11 +254,7 @@ public class PngEncoderFilterTests : MeasureFixture /// The current scanline. /// The preceding scanline. /// The component distance between adjacent pixels. - private static void AssertFilterMatchesReference( - PngFilterMethod filter, - byte[] scanline, - byte[] previousScanline, - int bytesPerPixel) + private static void AssertFilterMatchesReference(PngFilterMethod filter, byte[] scanline, byte[] previousScanline, int bytesPerPixel) { byte[] expected = new byte[scanline.Length + 1]; byte[] actual = new byte[scanline.Length + 1]; @@ -301,36 +274,16 @@ public class PngEncoderFilterTests : MeasureFixture break; case PngFilterMethod.Average: - ReferenceImplementations.EncodeAverageFilter( - scanline, - previousScanline, - expected, - bytesPerPixel, - out expectedSum); - - AverageFilter.Encode( - scanline, - previousScanline, - actual, - (uint)bytesPerPixel, - out actualSum); + ReferenceImplementations.EncodeAverageFilter(scanline, previousScanline, expected, bytesPerPixel, out expectedSum); + + AverageFilter.Encode(scanline, previousScanline, actual, (uint)bytesPerPixel, out actualSum); break; case PngFilterMethod.Paeth: - ReferenceImplementations.EncodePaethFilter( - scanline, - previousScanline, - expected, - bytesPerPixel, - out expectedSum); - - PaethFilter.Encode( - scanline, - previousScanline, - actual, - bytesPerPixel, - out actualSum); + ReferenceImplementations.EncodePaethFilter(scanline, previousScanline, expected, bytesPerPixel, out expectedSum); + + PaethFilter.Encode(scanline, previousScanline, actual, bytesPerPixel, out actualSum); break; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 448de9ec4..34606fcde 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -866,12 +866,7 @@ public class PixelBlenderTests /// The source opacity. /// The pixel coverage. /// The blended pixel. - private static TPixel BlendWithCoverageScalar( - PixelBlender blender, - TPixel background, - TPixel source, - float amount, - float coverage) + private static TPixel BlendWithCoverageScalar(PixelBlender blender, TPixel background, TPixel source, float amount, float coverage) where TPixel : unmanaged, IPixel { Span destination = stackalloc TPixel[1]; @@ -880,14 +875,7 @@ public class PixelBlenderTests Span coverageSpan = stackalloc float[1] { coverage }; Span buffer = stackalloc Vector4[3]; - blender.BlendWithCoverage( - Configuration.Default, - destination, - backgroundSpan, - sourceSpan, - amount, - coverageSpan, - buffer); + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer); return destination[0]; } diff --git a/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs index 52f45aad7..837d807f0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs @@ -20,24 +20,14 @@ public class Vector4ConvertersTests /// [Fact] public void MultiplyThenAddMatchesComponentArithmeticAcrossHardwareWidths() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertMultiplyThenAddMatchesComponentArithmetic, - HwIntrinsics.AllowAll - | HwIntrinsics.DisableAVX512F - | HwIntrinsics.DisableAVX - | HwIntrinsics.DisableHWIntrinsic); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertMultiplyThenAddMatchesComponentArithmetic, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); /// /// Verifies add-then-divide behavior for every SIMD boundary and the software fallback. /// [Fact] public void AddThenDivideMatchesComponentArithmeticAcrossHardwareWidths() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertAddThenDivideMatchesComponentArithmetic, - HwIntrinsics.AllowAll - | HwIntrinsics.DisableAVX512F - | HwIntrinsics.DisableAVX - | HwIntrinsics.DisableHWIntrinsic); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertAddThenDivideMatchesComponentArithmetic, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); /// /// Compares the multiply-then-add traversal with independently evaluated component expressions. @@ -56,11 +46,7 @@ public class Vector4ConvertersTests { Vector4 value = actual[i]; - expected[i] = new Vector4( - (value.X * multiplier.X) + offset.X, - (value.Y * multiplier.Y) + offset.Y, - (value.Z * multiplier.Z) + offset.Z, - (value.W * multiplier.W) + offset.W); + expected[i] = new Vector4((value.X * multiplier.X) + offset.X, (value.Y * multiplier.Y) + offset.Y, (value.Z * multiplier.Z) + offset.Z, (value.W * multiplier.W) + offset.W); } Vector4Converters.MultiplyThenAdd(actual, multiplier, offset); @@ -86,11 +72,7 @@ public class Vector4ConvertersTests { Vector4 value = actual[i]; - expected[i] = new Vector4( - (value.X + offset.X) / divisor.X, - (value.Y + offset.Y) / divisor.Y, - (value.Z + offset.Z) / divisor.Z, - (value.W + offset.W) / divisor.W); + expected[i] = new Vector4((value.X + offset.X) / divisor.X, (value.Y + offset.Y) / divisor.Y, (value.Z + offset.Z) / divisor.Z, (value.W + offset.W) / divisor.W); } Vector4Converters.AddThenDivide(actual, offset, divisor); @@ -110,11 +92,7 @@ public class Vector4ConvertersTests for (int i = 0; i < result.Length; i++) { - result[i] = new Vector4( - (i * 17F) - 31F, - (i * -23F) + 37F, - (i * .25F) - 41F, - (i * 3F) + 43F); + result[i] = new Vector4((i * 17F) - 31F, (i * -23F) + 37F, (i * .25F) - 41F, (i * 3F) + 43F); } return result; From 49d20566a366e1bbda1df9986ff8ffa784172b06 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:32:25 +1000 Subject: [PATCH 229/240] Use tensor addition for histogram offsets --- .../Common/Helpers/TensorPrimitives.cs | 12 +++++++++ .../Formats/Png/Filters/IPngFilterOperator.cs | 1 + .../Formats/Webp/Lossless/Vp8LHistogram.cs | 1 - .../HistogramEqualizationProcessor{TPixel}.cs | 6 ++--- .../Common/TensorPrimitivesTests.cs | 27 +++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs index 7f97776ab..bcfd16bb9 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs @@ -137,6 +137,18 @@ internal static class TensorPrimitives_ where T : IAdditionOperators, IAdditiveIdentity => InvokeSpanSpanIntoSpan>(x, y, destination); + /// + /// Computes the element-wise sum of the values in and the scalar . + /// + /// The element type. + /// The first addends. + /// The scalar second addend. + /// The destination for the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(ReadOnlySpan x, T y, Span destination) + where T : IAdditionOperators, IAdditiveIdentity + => InvokeSpanScalarIntoSpan>(x, y, destination); + /// /// Computes the element-wise result of dividing the values in by . /// diff --git a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs index c81187775..bae1ac477 100644 --- a/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs +++ b/src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Common.Helpers; diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index c59235e70..eca60dd3f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -533,7 +533,6 @@ internal abstract unsafe class Vp8LHistogram return cost; } - } internal sealed unsafe class OwnedVp8LHistogram : Vp8LHistogram, IDisposable diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index fd01bd8dd..b624d5b0e 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Normalization; @@ -115,10 +116,7 @@ internal abstract class HistogramEqualizationProcessor : ImageProcessor< int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; if (addToEachBin > 0) { - for (nuint i = 0; i < (uint)histogram.Length; i++) - { - Unsafe.Add(ref histogramBase, i) += addToEachBin; - } + TensorPrimitives_.Add(histogram, addToEachBin, histogram); } int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs index 1cc5b943f..375fc000d 100644 --- a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -90,6 +90,33 @@ public class TensorPrimitivesTests Assert.Equal(expected, x); } + /// + /// Verifies that scalar integer addition produces identical results for separate and in-place destinations. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void AddScalarInt32MatchesScalarFormula(int length) + { + int[] source = new int[length]; + int[] expected = new int[length]; + const int addend = 17; + + for (int i = 0; i < length; i++) + { + source[i] = (i * 37) - 200; + expected[i] = source[i] + addend; + } + + int[] destination = new int[length]; + TensorPrimitives_.Add(source, addend, destination); + Assert.Equal(expected, destination); + + int[] inPlace = (int[])source.Clone(); + TensorPrimitives_.Add(inPlace, addend, inPlace); + Assert.Equal(expected, inPlace); + } + /// /// Verifies that integer clamping produces identical results for separate and in-place destinations. /// From c1dc1aedb4e47bb53925952a27a7957f8a5cdc4f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:40:00 +1000 Subject: [PATCH 230/240] Use tensor negation for sharpen kernels --- .../Common/Helpers/TensorPrimitives.cs | 2 +- .../Helpers/TensorPrimitives_.Negate.cs | 276 ++++++++++++++++++ .../ConvolutionProcessorHelpers.cs | 20 +- .../Common/TensorPrimitivesTests.cs | 36 +++ .../ConvolutionProcessorHelpersTest.cs | 35 +++ 5 files changed, 355 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs index bcfd16bb9..4bff7882f 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Common.Helpers; /// implementation when ImageSharp no longer supports target frameworks that predate it. /// #pragma warning disable SA1649 // File name should match first type name -internal static class TensorPrimitives_ +internal static partial class TensorPrimitives_ #pragma warning restore SA1649 // File name should match first type name { /// diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs new file mode 100644 index 000000000..f61e4877c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs @@ -0,0 +1,276 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Defines an element-wise unary operation. + /// + /// The element type. + private interface IUnaryOperator + { + /// + /// Gets a value indicating whether the operation supports vector execution. + /// + public static abstract bool Vectorizable { get; } + + /// + /// Applies the operation to a scalar value. + /// + /// The input value. + /// The operation result. + public static abstract T Invoke(T x); + + /// + /// Applies the operation to a 128-bit vector. + /// + /// The input vector. + /// The operation result. + public static abstract Vector128 Invoke(Vector128 x); + + /// + /// Applies the operation to a 256-bit vector. + /// + /// The input vector. + /// The operation result. + public static abstract Vector256 Invoke(Vector256 x); + + /// + /// Applies the operation to a 512-bit vector. + /// + /// The input vector. + /// The operation result. + public static abstract Vector512 Invoke(Vector512 x); + } + + /// + /// Computes the element-wise negation of the values in . + /// + /// The element type. + /// The values to negate. + /// The destination for the negated values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Negate(ReadOnlySpan x, Span destination) + where T : IUnaryNegationOperators + => InvokeSpanIntoSpan>(x, destination); + + /// + /// Performs an element-wise unary operation over a span. + /// + /// The element type. + /// The operation to apply. + /// The input values. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeSpanIntoSpan(ReadOnlySpan x, Span destination) + where TOperator : struct, IUnaryOperator + { + ref T xRef = ref MemoryMarshal.GetReference(x); + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)x.Length; + + // The dispatch matches the other compatibility pipelines: AVX-512 is reserved for large spans because + // its setup cost is not recovered by the short image-processing buffers that dominate ImageSharp. + if (TOperator.Vectorizable + && Vector512.IsHardwareAccelerated + && Vector512.IsSupported + && length >= 512) + { + InvokeUnaryVectorized512(ref xRef, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256.IsSupported && length >= (uint)Vector256.Count) + { + InvokeUnaryVectorized256(ref xRef, ref destinationRef, length); + return; + } + + if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128.IsSupported && length >= (uint)Vector128.Count) + { + InvokeUnaryVectorized128(ref xRef, ref destinationRef, length); + return; + } + + for (nuint i = 0; i < length; i++) + { + Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i)); + } + } + + /// + /// Applies a unary operation with 128-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeUnaryVectorized128(ref T xRef, ref T destinationRef, nuint length) + where TOperator : struct, IUnaryOperator + { + nuint vectorCount = (uint)Vector128.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + + // The final vector overlaps the preceding store when the length is not a vector multiple. Loading it + // before any stores preserves same-start in-place operation because it captures the original tail. + Vector128 end = default; + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a unary operation with 256-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeUnaryVectorized256(ref T xRef, ref T destinationRef, nuint length) + where TOperator : struct, IUnaryOperator + { + nuint vectorCount = (uint)Vector256.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector256 end = default; + + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Applies a unary operation with 512-bit vectors. + /// + /// The element type. + /// The operation to apply. + /// The first input element. + /// The first destination element. + /// The number of elements to process. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InvokeUnaryVectorized512(ref T xRef, ref T destinationRef, nuint length) + where TOperator : struct, IUnaryOperator + { + nuint vectorCount = (uint)Vector512.Count; + nuint vectorsPerLoop = vectorCount * 8; + nuint index = 0; + Vector512 end = default; + + if ((length % vectorCount) != 0) + { + end = TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, length - vectorCount)); + } + + while ((length - index) >= vectorsPerLoop) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); + + index += vectorsPerLoop; + } + + while ((length - index) >= vectorCount) + { + TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); + index += vectorCount; + } + + if (index != length) + { + end.StoreUnsafe(ref destinationRef, length - vectorCount); + } + } + + /// + /// Implements element-wise negation for scalar and SIMD inputs. + /// + /// The element type. + private readonly struct NegateOperator : IUnaryOperator + where T : IUnaryNegationOperators + { + /// + public static bool Vectorizable => true; + + /// + public static T Invoke(T x) => -x; + + /// + public static Vector128 Invoke(Vector128 x) => -x; + + /// + public static Vector256 Invoke(Vector256 x) => -x; + + /// + public static Vector512 Invoke(Vector512 x) => -x; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index 4aefa0dae..52cd9dbc5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Processing.Processors.Convolution; @@ -68,19 +69,12 @@ internal static class ConvolutionProcessorHelpers // Invert the kernel for sharpening. int midpointRounded = (int)midpoint; - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[i] = (2F * sum) - kernel[i]; - } - else - { - // invert value - kernel[i] = -kernel[i]; - } - } + float midpointValue = kernel[midpointRounded]; + TensorPrimitives_.Negate(kernel, kernel); + + // The sharpening kernel negates every Gaussian weight except its center. Restore that original + // center while adding twice the Gaussian sum so the complete kernel retains unit response. + kernel[midpointRounded] = (2F * sum) - midpointValue; // Normalize kernel so that the sum of all weights equals 1 for (int i = 0; i < size; i++) diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs index 375fc000d..1fe64893b 100644 --- a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -117,6 +117,42 @@ public class TensorPrimitivesTests Assert.Equal(expected, inPlace); } + /// + /// Verifies that floating-point negation preserves the scalar operator's exact bit-level behavior. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void NegateSingleMatchesScalarFormula(int length) + { + float[] values = + { + float.NaN, + -0F, + 0F, + -1F, + 1F, + float.NegativeInfinity, + float.PositiveInfinity + }; + + float[] source = new float[length]; + float[] expected = new float[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = values[i % values.Length]; + expected[i] = -source[i]; + } + + float[] destination = new float[length]; + TensorPrimitives_.Negate(source, destination); + AssertSingleBitsEqual(expected, destination); + + TensorPrimitives_.Negate(source, source); + AssertSingleBitsEqual(expected, source); + } + /// /// Verifies that integer clamping produces identical results for separate and in-place destinations. /// diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs index 574c98371..38c635004 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs @@ -41,6 +41,41 @@ public class ConvolutionProcessorHelpersTest } } + /// + /// Verifies that Gaussian sharpening preserves the scalar kernel formula across scalar and SIMD lengths. + /// + /// The kernel radius. + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(9)] + [InlineData(32)] + [InlineData(80)] + public void VerifyGaussianSharpenKernel(int radius) + { + int kernelSize = (radius * 2) + 1; + float sigma = radius / 3F; + float[] expected = new float[kernelSize]; + float sum = 0F; + + for (int i = 0; i < kernelSize; i++) + { + float value = Numerics.Gaussian(i - radius, sigma); + expected[i] = value; + sum += value; + } + + for (int i = 0; i < kernelSize; i++) + { + expected[i] = i == radius ? (2F * sum) - expected[i] : -expected[i]; + expected[i] /= sum; + } + + float[] actual = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, sigma); + + Assert.Equal(expected, actual); + } + [Fact] public void VerifyNonSeparableMatrix() { From 41f17ba6b575ea5604dd56e774695b85f9e781ff Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:43:27 +1000 Subject: [PATCH 231/240] Use tensor division for Gaussian kernels --- .../Convolution/ConvolutionProcessorHelpers.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index 52cd9dbc5..db9eff55c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -37,11 +37,8 @@ internal static class ConvolutionProcessorHelpers kernel[i] = gx; } - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } + // Divide every weight by the accumulated Gaussian sum so the kernel has unit response. + TensorPrimitives_.Divide(kernel, sum, kernel); return kernel; } @@ -76,11 +73,8 @@ internal static class ConvolutionProcessorHelpers // center while adding twice the Gaussian sum so the complete kernel retains unit response. kernel[midpointRounded] = (2F * sum) - midpointValue; - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } + // Sharpening changes signs but preserves the Gaussian sum, so the same divisor produces unit response. + TensorPrimitives_.Divide(kernel, sum, kernel); return kernel; } From 43d99b46dc19bf7c7f2aa43ed158bb6be57b326d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Jul 2026 23:47:25 +1000 Subject: [PATCH 232/240] Use tensor multiplication for Bokeh kernels --- .../Parameters/BokehBlurKernelDataProvider.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index 4b3dd7fe7..cf2bec448 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -209,13 +210,11 @@ internal static class BokehBlurKernelDataProvider for (int i = 0; i < kernelsSpan.Length; i++) { ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, (uint)i); - int length = kernelsRef.Length; - ref Complex64 valueRef = ref MemoryMarshal.GetArrayDataReference(kernelsRef); - for (int j = 0; j < length; j++) - { - Unsafe.Add(ref valueRef, (uint)j) *= scalar; - } + // Complex64 stores each value as adjacent real and imaginary floats. Multiplying that flattened + // float span scales both parts independently, which is exactly complex multiplication by a real scalar. + Span values = MemoryMarshal.Cast(kernelsRef); + TensorPrimitives_.Multiply(values, scalar, values); } } } From 368498133a4d63752c22de90d28e89aa9b7924ff Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 00:19:27 +1000 Subject: [PATCH 233/240] Fix Bokeh normalization on .NET 10 --- .../Convolution/Parameters/BokehBlurKernelDataProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index cf2bec448..12f6e55d5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -213,7 +213,7 @@ internal static class BokehBlurKernelDataProvider // Complex64 stores each value as adjacent real and imaginary floats. Multiplying that flattened // float span scales both parts independently, which is exactly complex multiplication by a real scalar. - Span values = MemoryMarshal.Cast(kernelsRef); + Span values = MemoryMarshal.Cast(kernelsRef.AsSpan()); TensorPrimitives_.Multiply(values, scalar, values); } } From 267802a4b659b2ebc5abfc37a14d25e14396bc74 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 00:20:39 +1000 Subject: [PATCH 234/240] Optimize ICC LUT normalization --- .../Metadata/Profiles/ICC/Various/IccLut.cs | 14 +- .../Profiles/ICC/Various/IccLutNormalizer.cs | 253 ++++++++++++++++++ .../Profiles/ICC/Various/IccLutTests.cs | 108 ++++++++ 3 files changed, 363 insertions(+), 12 deletions(-) create mode 100644 src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs create mode 100644 tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs index 5f07e5589..fb47409be 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs @@ -23,13 +23,8 @@ internal readonly struct IccLut : IEquatable { Guard.NotNull(values, nameof(values)); - const float max = ushort.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + IccLutNormalizer.Normalize(values, this.Values); } /// @@ -40,13 +35,8 @@ internal readonly struct IccLut : IEquatable { Guard.NotNull(values, nameof(values)); - const float max = byte.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + IccLutNormalizer.Normalize(values, this.Values); } /// diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs new file mode 100644 index 000000000..67796cad3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs @@ -0,0 +1,253 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Converts integer ICC lookup-table entries to their normalized single-precision representation. +/// +internal static class IccLutNormalizer +{ + /// + /// Defines the scalar and SIMD conversion for an integer lookup-table element type. + /// + /// The integer element type. + private interface INormalizeOperator + where T : unmanaged + { + /// + /// Gets the divisor that maps the integer range to [0, 1]. + /// + public static abstract float Divisor { get; } + + /// + /// Converts one scalar value. + /// + /// The integer value. + /// The normalized value. + public static abstract float Invoke(T source); + + /// + /// Converts one 128-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector128 source, Vector128 divisor, ref float destination); + + /// + /// Converts one 256-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector256 source, Vector256 divisor, ref float destination); + + /// + /// Converts one 512-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector512 source, Vector512 divisor, ref float destination); + } + + /// + /// Converts byte lookup-table entries to normalized single-precision values. + /// + /// The integer lookup-table entries. + /// The normalized destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Normalize(ReadOnlySpan source, Span destination) + => Normalize(source, destination); + + /// + /// Converts unsigned-short lookup-table entries to normalized single-precision values. + /// + /// The integer lookup-table entries. + /// The normalized destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Normalize(ReadOnlySpan source, Span destination) + => Normalize(source, destination); + + /// + /// Converts an integer lookup table using the widest available portable SIMD width, followed by narrower + /// widths and a scalar remainder. + /// + /// The integer element type. + /// The conversion implementation. + /// The integer lookup-table entries. + /// The normalized destination values. + private static void Normalize(ReadOnlySpan source, Span destination) + where T : unmanaged + where TOperator : struct, INormalizeOperator + { + ref T sourceRef = ref MemoryMarshal.GetReference(source); + ref float destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)source.Length; + nuint index = 0; + + if (Vector512.IsHardwareAccelerated) + { + Vector512 divisor = Vector512.Create(TOperator.Divisor); + nuint count = (uint)Vector512.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector512.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + if (Vector256.IsHardwareAccelerated) + { + Vector256 divisor = Vector256.Create(TOperator.Divisor); + nuint count = (uint)Vector256.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector256.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + if (Vector128.IsHardwareAccelerated) + { + Vector128 divisor = Vector128.Create(TOperator.Divisor); + nuint count = (uint)Vector128.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector128.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + // Preserve the scalar division expression for the final partial vector. Multiplication by a reciprocal + // is not bit-equivalent for every input and would change the values stored in the ICC profile model. + while (index < length) + { + Unsafe.Add(ref destinationRef, index) = TOperator.Invoke(Unsafe.Add(ref sourceRef, index)); + index++; + } + } + + /// + /// Converts packed byte entries to normalized single-precision values. + /// + private readonly struct ByteNormalizeOperator : INormalizeOperator + { + /// + public static float Divisor => byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(byte source) + => source / (float)byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector128 source, Vector128 divisor, ref float destination) + { + // [b0..b15] becomes four ordered groups of four UInt32 values. Every widened value is at most + // 255, so reinterpreting UInt32 as Int32 before conversion preserves its numeric value. + (Vector128 lower16, Vector128 upper16) = Vector128.Widen(source); + (Vector128 values0, Vector128 values1) = Vector128.Widen(lower16); + (Vector128 values2, Vector128 values3) = Vector128.Widen(upper16); + + (Vector128.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector128.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); + (Vector128.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + (Vector128.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 12); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector256 source, Vector256 divisor, ref float destination) + { + // [b0..b31] becomes four ordered groups of eight UInt32 values, matching four contiguous + // Vector256 stores without shuffling the converted results. + (Vector256 lower16, Vector256 upper16) = Vector256.Widen(source); + (Vector256 values0, Vector256 values1) = Vector256.Widen(lower16); + (Vector256 values2, Vector256 values3) = Vector256.Widen(upper16); + + (Vector256.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector256.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + (Vector256.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + (Vector256.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 24); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector512 source, Vector512 divisor, ref float destination) + { + // [b0..b63] becomes four ordered groups of sixteen UInt32 values, matching four contiguous + // Vector512 stores. The portable widening APIs map to zero-extension instructions. + (Vector512 lower16, Vector512 upper16) = Vector512.Widen(source); + (Vector512 values0, Vector512 values1) = Vector512.Widen(lower16); + (Vector512 values2, Vector512 values3) = Vector512.Widen(upper16); + + (Vector512.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector512.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + (Vector512.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 32); + (Vector512.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 48); + } + } + + /// + /// Converts packed unsigned-short entries to normalized single-precision values. + /// + private readonly struct UInt16NormalizeOperator : INormalizeOperator + { + /// + public static float Divisor => ushort.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(ushort source) + => source / (float)ushort.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector128 source, Vector128 divisor, ref float destination) + { + // [u0..u7] becomes two ordered groups of four UInt32 values. Every value is at most 65535, + // so signed conversion after reinterpretation is numerically identical to unsigned conversion. + (Vector128 lower, Vector128 upper) = Vector128.Widen(source); + + (Vector128.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector128.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector256 source, Vector256 divisor, ref float destination) + { + // [u0..u15] becomes two ordered groups of eight UInt32 values, matching two contiguous + // Vector256 stores without a result shuffle. + (Vector256 lower, Vector256 upper) = Vector256.Widen(source); + + (Vector256.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector256.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector512 source, Vector512 divisor, ref float destination) + { + // [u0..u31] becomes two ordered groups of sixteen UInt32 values, matching two contiguous + // Vector512 stores. The portable widening APIs map to zero-extension instructions. + (Vector512 lower, Vector512 upper) = Vector512.Widen(source); + + (Vector512.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector512.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs new file mode 100644 index 000000000..f6705a1d0 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various; + +[Trait("Profile", "Icc")] +public class IccLutTests +{ + /// + /// Gets lengths that exercise scalar execution, every SIMD width, mixed-width remainders, and multiple vectors. + /// + public static TheoryData LutLengths => new() + { + 0, + 1, + 7, + 8, + 9, + 15, + 16, + 17, + 31, + 32, + 33, + 63, + 64, + 65, + 255, + 256, + 257 + }; + + /// + /// Verifies that byte LUT construction preserves the scalar conversion result for every traversal shape. + /// + /// The number of LUT entries. + [Theory] + [MemberData(nameof(LutLengths))] + public void ByteConstructorMatchesScalarFormula(int length) + { + byte[] values = new byte[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (byte)((i * 73) + 19); + } + + IccLut actual = new(values); + + Assert.Equal(values.Length, actual.Values.Length); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)byte.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } + + /// + /// Verifies that unsigned-short LUT construction preserves the scalar conversion result for every traversal shape. + /// + /// The number of LUT entries. + [Theory] + [MemberData(nameof(LutLengths))] + public void UInt16ConstructorMatchesScalarFormula(int length) + { + ushort[] values = new ushort[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (ushort)((i * 12_347) + 1_019); + } + + IccLut actual = new(values); + + Assert.Equal(values.Length, actual.Values.Length); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)ushort.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } + + /// + /// Verifies bit-exact normalization for every possible unsigned-short input. + /// + [Fact] + public void UInt16ConstructorMatchesScalarFormulaForEveryValue() + { + ushort[] values = new ushort[ushort.MaxValue + 1]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (ushort)i; + } + + IccLut actual = new(values); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)ushort.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } +} From fb7ed7262917f7307c91d1f834a36f663347d1b5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 01:46:29 +1000 Subject: [PATCH 235/240] Use AVX-512 for byte tensor addition --- src/ImageSharp/Common/Helpers/TensorPrimitives.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs index 4bff7882f..4be4ab3aa 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives.cs @@ -204,15 +204,15 @@ internal static partial class TensorPrimitives_ ref T yRef = ref MemoryMarshal.GetReference(y); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; + nuint vector512Threshold = Unsafe.SizeOf() == 1 ? (uint)Vector512.Count : 512; - // AVX-512 setup only pays off for larger multi-byte inputs. Byte addition remains on AVX2 because direct - // PNG/WebP measurements show that its higher lane count does not recover the wider dispatch cost. + // Match the runtime's AVX-512 selection for byte-sized elements once one complete vector is available. + // Wider elements retain the measured crossover point where their 512-bit setup cost becomes worthwhile. // Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct. if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported - && Unsafe.SizeOf() > 1 - && length >= 512) + && length >= vector512Threshold) { InvokeVectorized512(ref xRef, ref yRef, ref destinationRef, length); return; From 979a9237c59e0087c2ecd23b4f2eac9f2df4b823 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 03:07:53 +1000 Subject: [PATCH 236/240] Align tensor primitive dispatch with runtime --- .../Common/Helpers/TensorPrimitives_.Add.cs | 84 ++ .../Common/Helpers/TensorPrimitives_.Clamp.cs | 318 ++++++++ .../Helpers/TensorPrimitives_.Divide.cs | 83 ++ ...itives.cs => TensorPrimitives_.Helpers.cs} | 768 +----------------- .../Common/Helpers/TensorPrimitives_.Max.cs | 245 ++++++ .../Helpers/TensorPrimitives_.Multiply.cs | 72 ++ .../Helpers/TensorPrimitives_.Negate.cs | 5 +- 7 files changed, 811 insertions(+), 764 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs rename src/ImageSharp/Common/Helpers/{TensorPrimitives.cs => TensorPrimitives_.Helpers.cs} (55%) create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs create mode 100644 src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs new file mode 100644 index 000000000..f2ef214cf --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs @@ -0,0 +1,84 @@ +// 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.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Computes the element-wise sum of the values in and . + /// + /// The element type. + /// The first addends. + /// The second addends. + /// The destination for the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(ReadOnlySpan x, ReadOnlySpan y, Span destination) + where T : IAdditionOperators, IAdditiveIdentity + => InvokeSpanSpanIntoSpan>(x, y, destination); + + /// + /// Computes the element-wise sum of the values in and the scalar . + /// + /// The element type. + /// The first addends. + /// The scalar second addend. + /// The destination for the sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(ReadOnlySpan x, T y, Span destination) + where T : IAdditionOperators, IAdditiveIdentity + => InvokeSpanScalarIntoSpan>(x, y, destination); + + /// + /// Adds corresponding values. + /// + /// The element type. + private readonly struct AddOperator : IBinaryOperator + where T : IAdditionOperators, IAdditiveIdentity + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Adds scalar values. + /// + /// The first addend. + /// The second addend. + /// The sum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x + y; + + /// + /// Adds 128-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x + y; + + /// + /// Adds 256-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x + y; + + /// + /// Adds 512-bit vectors. + /// + /// The first addends. + /// The second addends. + /// The sums. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x + y; + } +} diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs new file mode 100644 index 000000000..e2cc9097e --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.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.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Computes the element-wise result of clamping to the inclusive range specified + /// by and . + /// + /// The element type. + /// The values to clamp. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The destination for the clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(ReadOnlySpan x, T min, T max, Span destination) + where T : INumber + => InvokeSpanScalarScalarIntoSpan>(x, min, max, destination); + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampSingle( + Vector128 value, + Vector128 min, + Vector128 max) + { + // Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the + // first operand and select negative zero when equal values have different signs. + Vector128 maximum = Vector128.ConditionalSelect( + Vector128.LessThan(min, value) + | ~Vector128.Equals(value, value) + | (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector128.ConditionalSelect( + Vector128.LessThan(maximum, max) + | ~Vector128.Equals(maximum, maximum) + | (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampSingle( + Vector256 value, + Vector256 min, + Vector256 max) + { + Vector256 maximum = Vector256.ConditionalSelect( + Vector256.LessThan(min, value) + | ~Vector256.Equals(value, value) + | (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector256.ConditionalSelect( + Vector256.LessThan(maximum, max) + | ~Vector256.Equals(maximum, maximum) + | (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps single-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampSingle( + Vector512 value, + Vector512 min, + Vector512 max) + { + Vector512 maximum = Vector512.ConditionalSelect( + Vector512.LessThan(min, value) + | ~Vector512.Equals(value, value) + | (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), + value, + min); + + return Vector512.ConditionalSelect( + Vector512.LessThan(maximum, max) + | ~Vector512.Equals(maximum, maximum) + | (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampDouble( + Vector128 value, + Vector128 min, + Vector128 max) + { + Vector128 maximum = Vector128.ConditionalSelect( + Vector128.LessThan(min, value) + | ~Vector128.Equals(value, value) + | (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector128.ConditionalSelect( + Vector128.LessThan(maximum, max) + | ~Vector128.Equals(maximum, maximum) + | (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampDouble( + Vector256 value, + Vector256 min, + Vector256 max) + { + Vector256 maximum = Vector256.ConditionalSelect( + Vector256.LessThan(min, value) + | ~Vector256.Equals(value, value) + | (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector256.ConditionalSelect( + Vector256.LessThan(maximum, max) + | ~Vector256.Equals(maximum, maximum) + | (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Clamps double-precision values with the normalized runtime semantics. + /// + /// The values to clamp. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampDouble( + Vector512 value, + Vector512 min, + Vector512 max) + { + Vector512 maximum = Vector512.ConditionalSelect( + Vector512.LessThan(min, value) + | ~Vector512.Equals(value, value) + | (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), + value, + min); + + return Vector512.ConditionalSelect( + Vector512.LessThan(maximum, max) + | ~Vector512.Equals(maximum, maximum) + | (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), + maximum, + max); + } + + /// + /// Clamps values using the complete runtime tensor contract, including signed-zero correction. + /// + /// The element type. + private readonly struct ClampOperator : ITernaryOperator + where T : INumber + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Clamps a scalar value. + /// + /// The value. + /// The inclusive lower bound. + /// The inclusive upper bound. + /// The clamped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T min, T max) + => Vector128.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max); + + /// + /// Clamps a 128-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 min, Vector128 max) + { + if (typeof(T) == typeof(float)) + { + Vector128 result = ClampSingle( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref min), + Unsafe.As, Vector128>(ref max)); + + return Unsafe.As, Vector128>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector128 result = ClampDouble( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref min), + Unsafe.As, Vector128>(ref max)); + + return Unsafe.As, Vector128>(ref result); + } + + return Vector128_.Clamp(x, min, max); + } + + /// + /// Clamps a 256-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 min, Vector256 max) + { + if (typeof(T) == typeof(float)) + { + Vector256 result = ClampSingle( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref min), + Unsafe.As, Vector256>(ref max)); + + return Unsafe.As, Vector256>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector256 result = ClampDouble( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref min), + Unsafe.As, Vector256>(ref max)); + + return Unsafe.As, Vector256>(ref result); + } + + return Vector256_.Clamp(x, min, max); + } + + /// + /// Clamps a 512-bit vector. + /// + /// The values. + /// The inclusive lower bounds. + /// The inclusive upper bounds. + /// The clamped values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 min, Vector512 max) + { + if (typeof(T) == typeof(float)) + { + Vector512 result = ClampSingle( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref min), + Unsafe.As, Vector512>(ref max)); + + return Unsafe.As, Vector512>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector512 result = ClampDouble( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref min), + Unsafe.As, Vector512>(ref max)); + + return Unsafe.As, Vector512>(ref result); + } + + return Vector512_.Clamp(x, min, max); + } + } +} diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs new file mode 100644 index 000000000..e40a26cea --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs @@ -0,0 +1,83 @@ +// 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.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Computes the element-wise result of dividing the values in by . + /// + /// The element type. + /// The dividend values. + /// The divisor. + /// The destination for the quotient values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Divide(ReadOnlySpan x, T y, Span destination) + where T : IDivisionOperators + => InvokeSpanScalarIntoSpanForDivision>(x, y, destination); + + /// + /// Determines whether has the same vector division support as . + /// + /// The element type. + /// when is a 32-bit signed native integer type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInt32Like() + => typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint)); + + /// + /// Divides values by a scalar. + /// + /// The element type. + private readonly struct DivideOperator : IBinaryOperator + where T : IDivisionOperators + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => typeof(T) == typeof(float) + || typeof(T) == typeof(double) + || (Vector256.IsHardwareAccelerated && IsInt32Like()); + + /// + /// Divides scalar values. + /// + /// The dividend. + /// The divisor. + /// The quotient. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x / y; + + /// + /// Divides 128-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x / y; + + /// + /// Divides 256-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x / y; + + /// + /// Divides 512-bit vectors. + /// + /// The dividends. + /// The divisors. + /// The quotients. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x / y; + } +} diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs similarity index 55% rename from src/ImageSharp/Common/Helpers/TensorPrimitives.cs rename to src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs index 4be4ab3aa..3ba1b61af 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs @@ -15,9 +15,7 @@ namespace SixLabors.ImageSharp.Common.Helpers; /// The API shape follows System.Numerics.Tensors.TensorPrimitives so call sites can move to the runtime /// implementation when ImageSharp no longer supports target frameworks that predate it. /// -#pragma warning disable SA1649 // File name should match first type name internal static partial class TensorPrimitives_ -#pragma warning restore SA1649 // File name should match first type name { /// /// Defines an element-wise binary operation. @@ -111,80 +109,6 @@ internal static partial class TensorPrimitives_ public static abstract Vector512 Invoke(Vector512 x, Vector512 y, Vector512 z); } - /// - /// Computes the element-wise result of clamping to the inclusive range specified - /// by and . - /// - /// The element type. - /// The values to clamp. - /// The inclusive lower bound. - /// The inclusive upper bound. - /// The destination for the clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(ReadOnlySpan x, T min, T max, Span destination) - where T : INumber - => InvokeSpanScalarScalarIntoSpan>(x, min, max, destination); - - /// - /// Computes the element-wise sum of the values in and . - /// - /// The element type. - /// The first addends. - /// The second addends. - /// The destination for the sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(ReadOnlySpan x, ReadOnlySpan y, Span destination) - where T : IAdditionOperators, IAdditiveIdentity - => InvokeSpanSpanIntoSpan>(x, y, destination); - - /// - /// Computes the element-wise sum of the values in and the scalar . - /// - /// The element type. - /// The first addends. - /// The scalar second addend. - /// The destination for the sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(ReadOnlySpan x, T y, Span destination) - where T : IAdditionOperators, IAdditiveIdentity - => InvokeSpanScalarIntoSpan>(x, y, destination); - - /// - /// Computes the element-wise result of dividing the values in by . - /// - /// The element type. - /// The dividend values. - /// The divisor. - /// The destination for the quotient values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Divide(ReadOnlySpan x, T y, Span destination) - where T : IDivisionOperators - => InvokeSpanScalarIntoSpanForDivision>(x, y, destination); - - /// - /// Computes the element-wise maximum of the values in and . - /// - /// The element type. - /// The values to compare. - /// The value to compare with each element. - /// The destination for the maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Max(ReadOnlySpan x, T y, Span destination) - where T : INumber - => InvokeSpanScalarIntoSpan>(x, y, destination); - - /// - /// Computes the element-wise product of the values in and . - /// - /// The element type. - /// The multiplicands. - /// The multiplier. - /// The destination for the products. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Multiply(ReadOnlySpan x, T y, Span destination) - where T : IMultiplyOperators, IMultiplicativeIdentity - => InvokeSpanScalarIntoSpan>(x, y, destination); - /// /// Performs an element-wise binary operation between two spans. /// @@ -204,15 +128,13 @@ internal static partial class TensorPrimitives_ ref T yRef = ref MemoryMarshal.GetReference(y); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; - nuint vector512Threshold = Unsafe.SizeOf() == 1 ? (uint)Vector512.Count : 512; - // Match the runtime's AVX-512 selection for byte-sized elements once one complete vector is available. - // Wider elements retain the measured crossover point where their 512-bit setup cost becomes worthwhile. + // Runtime main selects the widest supported pipeline once one complete vector is available. // Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct. if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported - && length >= vector512Threshold) + && length >= (uint)Vector512.Count) { InvokeVectorized512(ref xRef, ref yRef, ref destinationRef, length); return; @@ -255,12 +177,11 @@ internal static partial class TensorPrimitives_ ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; - // The runtime-style unrolled 512-bit pipeline wins on large inputs, but its setup cost regresses the - // shorter JPEG and ICC buffers. Measurements put the crossover safely below 512 elements. + // Runtime main selects the widest supported pipeline once one complete vector is available. if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported - && length >= 512) + && length >= (uint)Vector512.Count) { InvokeVectorized512(ref xRef, y, ref destinationRef, length); return; @@ -285,7 +206,7 @@ internal static partial class TensorPrimitives_ } /// - /// Performs element-wise division using thresholds measured for ImageSharp normalization workloads. + /// Performs element-wise division using the runtime tensor width-selection order. /// /// The element type. /// The division operation to apply. @@ -303,13 +224,11 @@ internal static partial class TensorPrimitives_ ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; - // AVX-512 only wins once there is enough work to amortize its wider dispatch and division latency. - // Eight vectors is also the runtime pipeline's unrolled-loop boundary, while shorter inputs retain - // the lower setup cost of 256-bit vectors. + // Runtime main selects the widest supported pipeline once one complete vector is available. if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported - && length >= (uint)(Vector512.Count * 8)) + && length >= (uint)Vector512.Count) { InvokeVectorized512(ref xRef, y, ref destinationRef, length); return; @@ -726,116 +645,6 @@ internal static partial class TensorPrimitives_ } } - /// - /// Selects maximum single-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 MaxSingle(Vector128 x, Vector128 y) - { - // The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a - // first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order. - Vector128 result = Vector128.Max(x, y); - result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); - - return Vector128.ConditionalSelect( - Vector128.Equals(x, y), - x & y, - result); - } - - /// - /// Selects maximum single-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 MaxSingle(Vector256 x, Vector256 y) - { - Vector256 result = Vector256.Max(x, y); - result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); - - return Vector256.ConditionalSelect( - Vector256.Equals(x, y), - x & y, - result); - } - - /// - /// Selects maximum single-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 MaxSingle(Vector512 x, Vector512 y) - { - Vector512 result = Vector512.Max(x, y); - result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); - - return Vector512.ConditionalSelect( - Vector512.Equals(x, y), - x & y, - result); - } - - /// - /// Selects maximum double-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 MaxDouble(Vector128 x, Vector128 y) - { - Vector128 result = Vector128.Max(x, y); - result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); - - return Vector128.ConditionalSelect( - Vector128.Equals(x, y), - x & y, - result); - } - - /// - /// Selects maximum double-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 MaxDouble(Vector256 x, Vector256 y) - { - Vector256 result = Vector256.Max(x, y); - result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); - - return Vector256.ConditionalSelect( - Vector256.Equals(x, y), - x & y, - result); - } - - /// - /// Selects maximum double-precision values with the normalized runtime semantics. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 MaxDouble(Vector512 x, Vector512 y) - { - Vector512 result = Vector512.Max(x, y); - result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); - - return Vector512.ConditionalSelect( - Vector512.Equals(x, y), - x & y, - result); - } - /// /// Applies a ternary operation with 128-bit vectors. /// @@ -1012,567 +821,4 @@ internal static partial class TensorPrimitives_ end.StoreUnsafe(ref destinationRef, length - vectorCount); } } - - /// - /// Clamps single-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ClampSingle( - Vector128 value, - Vector128 min, - Vector128 max) - { - // Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the - // first operand and select negative zero when equal values have different signs. - Vector128 maximum = Vector128.ConditionalSelect( - Vector128.LessThan(min, value) - | ~Vector128.Equals(value, value) - | (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), - value, - min); - - return Vector128.ConditionalSelect( - Vector128.LessThan(maximum, max) - | ~Vector128.Equals(maximum, maximum) - | (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), - maximum, - max); - } - - /// - /// Clamps single-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ClampSingle( - Vector256 value, - Vector256 min, - Vector256 max) - { - Vector256 maximum = Vector256.ConditionalSelect( - Vector256.LessThan(min, value) - | ~Vector256.Equals(value, value) - | (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), - value, - min); - - return Vector256.ConditionalSelect( - Vector256.LessThan(maximum, max) - | ~Vector256.Equals(maximum, maximum) - | (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), - maximum, - max); - } - - /// - /// Clamps single-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 ClampSingle( - Vector512 value, - Vector512 min, - Vector512 max) - { - Vector512 maximum = Vector512.ConditionalSelect( - Vector512.LessThan(min, value) - | ~Vector512.Equals(value, value) - | (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), - value, - min); - - return Vector512.ConditionalSelect( - Vector512.LessThan(maximum, max) - | ~Vector512.Equals(maximum, maximum) - | (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), - maximum, - max); - } - - /// - /// Clamps double-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 ClampDouble( - Vector128 value, - Vector128 min, - Vector128 max) - { - Vector128 maximum = Vector128.ConditionalSelect( - Vector128.LessThan(min, value) - | ~Vector128.Equals(value, value) - | (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), - value, - min); - - return Vector128.ConditionalSelect( - Vector128.LessThan(maximum, max) - | ~Vector128.Equals(maximum, maximum) - | (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), - maximum, - max); - } - - /// - /// Clamps double-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ClampDouble( - Vector256 value, - Vector256 min, - Vector256 max) - { - Vector256 maximum = Vector256.ConditionalSelect( - Vector256.LessThan(min, value) - | ~Vector256.Equals(value, value) - | (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), - value, - min); - - return Vector256.ConditionalSelect( - Vector256.LessThan(maximum, max) - | ~Vector256.Equals(maximum, maximum) - | (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), - maximum, - max); - } - - /// - /// Clamps double-precision values with the normalized runtime semantics. - /// - /// The values to clamp. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 ClampDouble( - Vector512 value, - Vector512 min, - Vector512 max) - { - Vector512 maximum = Vector512.ConditionalSelect( - Vector512.LessThan(min, value) - | ~Vector512.Equals(value, value) - | (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), - value, - min); - - return Vector512.ConditionalSelect( - Vector512.LessThan(maximum, max) - | ~Vector512.Equals(maximum, maximum) - | (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), - maximum, - max); - } - - /// - /// Determines whether has the same vector division support as . - /// - /// The element type. - /// when is a 32-bit signed native integer type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInt32Like() - => typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint)); - - /// - /// Adds corresponding values. - /// - /// The element type. - private readonly struct AddOperator : IBinaryOperator - where T : IAdditionOperators, IAdditiveIdentity - { - /// - /// Gets a value indicating whether this operation supports vector execution. - /// - public static bool Vectorizable => true; - - /// - /// Adds scalar values. - /// - /// The first addend. - /// The second addend. - /// The sum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Invoke(T x, T y) => x + y; - - /// - /// Adds 128-bit vectors. - /// - /// The first addends. - /// The second addends. - /// The sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Invoke(Vector128 x, Vector128 y) => x + y; - - /// - /// Adds 256-bit vectors. - /// - /// The first addends. - /// The second addends. - /// The sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Invoke(Vector256 x, Vector256 y) => x + y; - - /// - /// Adds 512-bit vectors. - /// - /// The first addends. - /// The second addends. - /// The sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Invoke(Vector512 x, Vector512 y) => x + y; - } - - /// - /// Clamps values using the complete runtime tensor contract, including signed-zero correction. - /// - /// The element type. - private readonly struct ClampOperator : ITernaryOperator - where T : INumber - { - /// - /// Gets a value indicating whether this operation supports vector execution. - /// - public static bool Vectorizable => true; - - /// - /// Clamps a scalar value. - /// - /// The value. - /// The inclusive lower bound. - /// The inclusive upper bound. - /// The clamped value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Invoke(T x, T min, T max) - => Vector128.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max); - - /// - /// Clamps a 128-bit vector. - /// - /// The values. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Invoke(Vector128 x, Vector128 min, Vector128 max) - { - if (typeof(T) == typeof(float)) - { - Vector128 result = ClampSingle( - Unsafe.As, Vector128>(ref x), - Unsafe.As, Vector128>(ref min), - Unsafe.As, Vector128>(ref max)); - - return Unsafe.As, Vector128>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector128 result = ClampDouble( - Unsafe.As, Vector128>(ref x), - Unsafe.As, Vector128>(ref min), - Unsafe.As, Vector128>(ref max)); - - return Unsafe.As, Vector128>(ref result); - } - - return Vector128_.Clamp(x, min, max); - } - - /// - /// Clamps a 256-bit vector. - /// - /// The values. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Invoke(Vector256 x, Vector256 min, Vector256 max) - { - if (typeof(T) == typeof(float)) - { - Vector256 result = ClampSingle( - Unsafe.As, Vector256>(ref x), - Unsafe.As, Vector256>(ref min), - Unsafe.As, Vector256>(ref max)); - - return Unsafe.As, Vector256>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector256 result = ClampDouble( - Unsafe.As, Vector256>(ref x), - Unsafe.As, Vector256>(ref min), - Unsafe.As, Vector256>(ref max)); - - return Unsafe.As, Vector256>(ref result); - } - - return Vector256_.Clamp(x, min, max); - } - - /// - /// Clamps a 512-bit vector. - /// - /// The values. - /// The inclusive lower bounds. - /// The inclusive upper bounds. - /// The clamped values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Invoke(Vector512 x, Vector512 min, Vector512 max) - { - if (typeof(T) == typeof(float)) - { - Vector512 result = ClampSingle( - Unsafe.As, Vector512>(ref x), - Unsafe.As, Vector512>(ref min), - Unsafe.As, Vector512>(ref max)); - - return Unsafe.As, Vector512>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector512 result = ClampDouble( - Unsafe.As, Vector512>(ref x), - Unsafe.As, Vector512>(ref min), - Unsafe.As, Vector512>(ref max)); - - return Unsafe.As, Vector512>(ref result); - } - - return Vector512_.Clamp(x, min, max); - } - } - - /// - /// Selects the maximum corresponding values. - /// - /// The element type. - private readonly struct MaxOperator : IBinaryOperator - where T : INumber - { - /// - /// Gets a value indicating whether this operation supports vector execution. - /// - public static bool Vectorizable => true; - - /// - /// Selects the maximum scalar value. - /// - /// The first value. - /// The second value. - /// The maximum value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Invoke(T x, T y) => T.Max(x, y); - - /// - /// Selects the maximum values from 128-bit vectors. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Invoke(Vector128 x, Vector128 y) - { - if (typeof(T) == typeof(float)) - { - Vector128 result = MaxSingle( - Unsafe.As, Vector128>(ref x), - Unsafe.As, Vector128>(ref y)); - - return Unsafe.As, Vector128>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector128 result = MaxDouble( - Unsafe.As, Vector128>(ref x), - Unsafe.As, Vector128>(ref y)); - - return Unsafe.As, Vector128>(ref result); - } - - return Vector128.Max(x, y); - } - - /// - /// Selects the maximum values from 256-bit vectors. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Invoke(Vector256 x, Vector256 y) - { - if (typeof(T) == typeof(float)) - { - Vector256 result = MaxSingle( - Unsafe.As, Vector256>(ref x), - Unsafe.As, Vector256>(ref y)); - - return Unsafe.As, Vector256>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector256 result = MaxDouble( - Unsafe.As, Vector256>(ref x), - Unsafe.As, Vector256>(ref y)); - - return Unsafe.As, Vector256>(ref result); - } - - return Vector256.Max(x, y); - } - - /// - /// Selects the maximum values from 512-bit vectors. - /// - /// The first values. - /// The second values. - /// The maximum values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Invoke(Vector512 x, Vector512 y) - { - if (typeof(T) == typeof(float)) - { - Vector512 result = MaxSingle( - Unsafe.As, Vector512>(ref x), - Unsafe.As, Vector512>(ref y)); - - return Unsafe.As, Vector512>(ref result); - } - - if (typeof(T) == typeof(double)) - { - Vector512 result = MaxDouble( - Unsafe.As, Vector512>(ref x), - Unsafe.As, Vector512>(ref y)); - - return Unsafe.As, Vector512>(ref result); - } - - return Vector512.Max(x, y); - } - } - - /// - /// Multiplies corresponding values. - /// - /// The element type. - private readonly struct MultiplyOperator : IBinaryOperator - where T : IMultiplyOperators, IMultiplicativeIdentity - { - /// - /// Gets a value indicating whether this operation supports vector execution. - /// - public static bool Vectorizable => true; - - /// - /// Multiplies scalar values. - /// - /// The multiplicand. - /// The multiplier. - /// The product. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Invoke(T x, T y) => x * y; - - /// - /// Multiplies 128-bit vectors. - /// - /// The multiplicands. - /// The multipliers. - /// The products. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Invoke(Vector128 x, Vector128 y) => x * y; - - /// - /// Multiplies 256-bit vectors. - /// - /// The multiplicands. - /// The multipliers. - /// The products. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Invoke(Vector256 x, Vector256 y) => x * y; - - /// - /// Multiplies 512-bit vectors. - /// - /// The multiplicands. - /// The multipliers. - /// The products. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Invoke(Vector512 x, Vector512 y) => x * y; - } - - /// - /// Divides values by a scalar. - /// - /// The element type. - private readonly struct DivideOperator : IBinaryOperator - where T : IDivisionOperators - { - /// - /// Gets a value indicating whether this operation supports vector execution. - /// - public static bool Vectorizable => typeof(T) == typeof(float) - || typeof(T) == typeof(double) - || (Vector256.IsHardwareAccelerated && IsInt32Like()); - - /// - /// Divides scalar values. - /// - /// The dividend. - /// The divisor. - /// The quotient. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Invoke(T x, T y) => x / y; - - /// - /// Divides 128-bit vectors. - /// - /// The dividends. - /// The divisors. - /// The quotients. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Invoke(Vector128 x, Vector128 y) => x / y; - - /// - /// Divides 256-bit vectors. - /// - /// The dividends. - /// The divisors. - /// The quotients. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Invoke(Vector256 x, Vector256 y) => x / y; - - /// - /// Divides 512-bit vectors. - /// - /// The dividends. - /// The divisors. - /// The quotients. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Invoke(Vector512 x, Vector512 y) => x / y; - } } diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs new file mode 100644 index 000000000..31312dddf --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs @@ -0,0 +1,245 @@ +// 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.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Computes the element-wise maximum of the values in and . + /// + /// The element type. + /// The values to compare. + /// The value to compare with each element. + /// The destination for the maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Max(ReadOnlySpan x, T y, Span destination) + where T : INumber + => InvokeSpanScalarIntoSpan>(x, y, destination); + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MaxSingle(Vector128 x, Vector128 y) + { + // The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a + // first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order. + Vector128 result = Vector128.Max(x, y); + result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); + + return Vector128.ConditionalSelect( + Vector128.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 MaxSingle(Vector256 x, Vector256 y) + { + Vector256 result = Vector256.Max(x, y); + result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); + + return Vector256.ConditionalSelect( + Vector256.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum single-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 MaxSingle(Vector512 x, Vector512 y) + { + Vector512 result = Vector512.Max(x, y); + result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); + + return Vector512.ConditionalSelect( + Vector512.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MaxDouble(Vector128 x, Vector128 y) + { + Vector128 result = Vector128.Max(x, y); + result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); + + return Vector128.ConditionalSelect( + Vector128.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 MaxDouble(Vector256 x, Vector256 y) + { + Vector256 result = Vector256.Max(x, y); + result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); + + return Vector256.ConditionalSelect( + Vector256.Equals(x, y), + x & y, + result); + } + + /// + /// Selects maximum double-precision values with the normalized runtime semantics. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 MaxDouble(Vector512 x, Vector512 y) + { + Vector512 result = Vector512.Max(x, y); + result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); + + return Vector512.ConditionalSelect( + Vector512.Equals(x, y), + x & y, + result); + } + + /// + /// Selects the maximum corresponding values. + /// + /// The element type. + private readonly struct MaxOperator : IBinaryOperator + where T : INumber + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Selects the maximum scalar value. + /// + /// The first value. + /// The second value. + /// The maximum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => T.Max(x, y); + + /// + /// Selects the maximum values from 128-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) + { + if (typeof(T) == typeof(float)) + { + Vector128 result = MaxSingle( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref y)); + + return Unsafe.As, Vector128>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector128 result = MaxDouble( + Unsafe.As, Vector128>(ref x), + Unsafe.As, Vector128>(ref y)); + + return Unsafe.As, Vector128>(ref result); + } + + return Vector128.Max(x, y); + } + + /// + /// Selects the maximum values from 256-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) + { + if (typeof(T) == typeof(float)) + { + Vector256 result = MaxSingle( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref y)); + + return Unsafe.As, Vector256>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector256 result = MaxDouble( + Unsafe.As, Vector256>(ref x), + Unsafe.As, Vector256>(ref y)); + + return Unsafe.As, Vector256>(ref result); + } + + return Vector256.Max(x, y); + } + + /// + /// Selects the maximum values from 512-bit vectors. + /// + /// The first values. + /// The second values. + /// The maximum values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) + { + if (typeof(T) == typeof(float)) + { + Vector512 result = MaxSingle( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref y)); + + return Unsafe.As, Vector512>(ref result); + } + + if (typeof(T) == typeof(double)) + { + Vector512 result = MaxDouble( + Unsafe.As, Vector512>(ref x), + Unsafe.As, Vector512>(ref y)); + + return Unsafe.As, Vector512>(ref result); + } + + return Vector512.Max(x, y); + } + } +} diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs new file mode 100644 index 000000000..deb1e922e --- /dev/null +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs @@ -0,0 +1,72 @@ +// 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.Common.Helpers; + +internal static partial class TensorPrimitives_ +{ + /// + /// Computes the element-wise product of the values in and . + /// + /// The element type. + /// The multiplicands. + /// The multiplier. + /// The destination for the products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Multiply(ReadOnlySpan x, T y, Span destination) + where T : IMultiplyOperators, IMultiplicativeIdentity + => InvokeSpanScalarIntoSpan>(x, y, destination); + + /// + /// Multiplies corresponding values. + /// + /// The element type. + private readonly struct MultiplyOperator : IBinaryOperator + where T : IMultiplyOperators, IMultiplicativeIdentity + { + /// + /// Gets a value indicating whether this operation supports vector execution. + /// + public static bool Vectorizable => true; + + /// + /// Multiplies scalar values. + /// + /// The multiplicand. + /// The multiplier. + /// The product. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Invoke(T x, T y) => x * y; + + /// + /// Multiplies 128-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Invoke(Vector128 x, Vector128 y) => x * y; + + /// + /// Multiplies 256-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Invoke(Vector256 x, Vector256 y) => x * y; + + /// + /// Multiplies 512-bit vectors. + /// + /// The multiplicands. + /// The multipliers. + /// The products. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Invoke(Vector512 x, Vector512 y) => x * y; + } +} diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs index f61e4877c..eb90a3903 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs @@ -76,12 +76,11 @@ internal static partial class TensorPrimitives_ ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; - // The dispatch matches the other compatibility pipelines: AVX-512 is reserved for large spans because - // its setup cost is not recovered by the short image-processing buffers that dominate ImageSharp. + // Runtime main selects the widest supported pipeline once one complete vector is available. if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512.IsSupported - && length >= 512) + && length >= (uint)Vector512.Count) { InvokeUnaryVectorized512(ref xRef, ref destinationRef, length); return; From b54c5caf5b155097c6e29ab16d8cb1ddf99d63d7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 09:41:53 +1000 Subject: [PATCH 237/240] Fix tensor negation on ARM64 --- .../Helpers/TensorPrimitives_.Negate.cs | 52 ++++++++++- .../Common/TensorPrimitivesTests.cs | 91 +++++++++++++++++-- 2 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs index eb90a3903..99dc80184 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs @@ -264,12 +264,58 @@ internal static partial class TensorPrimitives_ public static T Invoke(T x) => -x; /// - public static Vector128 Invoke(Vector128 x) => -x; + public static Vector128 Invoke(Vector128 x) + { + if (typeof(T) == typeof(float)) + { + // IEEE-754 negation toggles the sign bit. Expressing that operation explicitly avoids the + // subtraction-based ARM64 code generated by .NET 8 for generic vector negation, which loses + // the sign when +0F is negated and therefore differs from both scalar and runtime-main behavior. + return x ^ Vector128.Create(-0F).As(); + } + + if (typeof(T) == typeof(double)) + { + // Double-precision values use the same sign-bit representation, with the sign in bit 63. + return x ^ Vector128.Create(-0D).As(); + } + + return -x; + } /// - public static Vector256 Invoke(Vector256 x) => -x; + public static Vector256 Invoke(Vector256 x) + { + if (typeof(T) == typeof(float)) + { + // Keep the operation bitwise at every width so ARM64 preserves signed zero exactly. + return x ^ Vector256.Create(-0F).As(); + } + + if (typeof(T) == typeof(double)) + { + return x ^ Vector256.Create(-0D).As(); + } + + return -x; + } /// - public static Vector512 Invoke(Vector512 x) => -x; + public static Vector512 Invoke(Vector512 x) + { + if (typeof(T) == typeof(float)) + { + // Vector512 can be hardware accelerated directly or decomposed by the runtime; the explicit + // bit operation provides identical IEEE-754 behavior in either case. + return x ^ Vector512.Create(-0F).As(); + } + + if (typeof(T) == typeof(double)) + { + return x ^ Vector512.Create(-0D).As(); + } + + return -x; + } } } diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs index 1fe64893b..5ce5401f8 100644 --- a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -2,16 +2,14 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Common; public class TensorPrimitivesTests { - /// - /// Gets lengths that exercise scalar execution, every SIMD width, overlapping tails, and the unrolled loop. - /// - public static TheoryData SpanLengths => new() - { + private static readonly int[] SpanLengthValues = + [ 0, 1, 3, @@ -33,7 +31,52 @@ public class TensorPrimitivesTests 128, 129, 2048 - }; + ]; + + /// + /// Gets lengths that exercise scalar execution, every SIMD width, overlapping tails, and the unrolled loop. + /// + public static TheoryData SpanLengths => new(SpanLengthValues); + + /// + /// Verifies every compatibility operation while forcing the supported SIMD feature tiers in isolated processes. + /// + [Fact] + public void OperationsMatchScalarFormulasAcrossHardwareIntrinsicFeatures() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunOperationsAcrossHardwareIntrinsicFeatures, + HwIntrinsics.AllowAll + | HwIntrinsics.DisableAVX512F + | HwIntrinsics.DisableAVX + | HwIntrinsics.DisableArm64Sve + | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Runs the TensorPrimitives compatibility assertions inside a process configured for one hardware-intrinsic tier. + /// + private static void RunOperationsAcrossHardwareIntrinsicFeatures() + { + TensorPrimitivesTests tests = new(); + + // Reuse the focused assertions so the remote feature matrix cannot drift from the normal test coverage. + foreach (int length in SpanLengthValues) + { + tests.AddByteMatchesScalarFormula(length); + tests.AddUInt32MatchesScalarFormula(length); + tests.AddScalarInt32MatchesScalarFormula(length); + tests.NegateSingleMatchesScalarFormula(length); + tests.NegateDoubleMatchesScalarFormula(length); + tests.ClampInt32MatchesScalarFormula(length); + tests.ClampSingleMatchesRuntimeFormula(length); + tests.DivideSingleMatchesScalarFormula(length); + tests.MaxSingleMatchesRuntimeFormula(length); + tests.MultiplySingleMatchesScalarFormula(length); + tests.NormalizeMatchesScalarFormula(length); + } + + tests.ClampSinglePreservesRuntimeSpecialValueSemantics(); + tests.ClampDoublePreservesRuntimeSpecialValueSemantics(); + } /// /// Verifies that byte addition wraps modulo 256 and supports either input as the in-place destination. @@ -153,6 +196,42 @@ public class TensorPrimitivesTests AssertSingleBitsEqual(expected, source); } + /// + /// Verifies that double-precision negation preserves the scalar operator's exact bit-level behavior. + /// + /// The input length. + [Theory] + [MemberData(nameof(SpanLengths))] + public void NegateDoubleMatchesScalarFormula(int length) + { + double[] values = + { + double.NaN, + -0D, + 0D, + -1D, + 1D, + double.NegativeInfinity, + double.PositiveInfinity + }; + + double[] source = new double[length]; + double[] expected = new double[length]; + + for (int i = 0; i < source.Length; i++) + { + source[i] = values[i % values.Length]; + expected[i] = -source[i]; + } + + double[] destination = new double[length]; + TensorPrimitives_.Negate(source, destination); + AssertDoubleBitsEqual(expected, destination); + + TensorPrimitives_.Negate(source, source); + AssertDoubleBitsEqual(expected, source); + } + /// /// Verifies that integer clamping produces identical results for separate and in-place destinations. /// From 3c3d523954a44849d297707dc52594f78002daf3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 17:28:24 +1000 Subject: [PATCH 238/240] Flatten pixel blender hierarchy --- .../AssociatedAlphaPixelBlenders.Generated.cs | 1512 ++++++----------- .../AssociatedAlphaPixelBlenders.Generated.tt | 14 +- .../AssociatedAlphaPixelBlenders.cs | 216 +-- ...atedAlphaPixelBlender{TPixel,TOperator}.cs | 49 - .../DefaultPixelBlenders.Generated.cs | 1512 ++++++----------- .../DefaultPixelBlenders.Generated.tt | 14 +- .../PixelBlenders/IPixelBlenderOperator.cs | 5 + .../PixelBlender{TPixel,TOperator}.cs | 86 +- .../PixelOperations{TPixel}.PixelBenders.cs | 216 +-- .../PixelFormats/PixelBlenderTests.cs | 278 +-- .../PorterDuffFunctionsTestsTPixel.cs | 36 +- 11 files changed, 1534 insertions(+), 2404 deletions(-) delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs index d31e07c62..acd9fba9c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -17,6 +17,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); @@ -35,6 +38,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplySrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); @@ -53,6 +59,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); @@ -71,6 +80,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); @@ -89,6 +101,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); @@ -107,6 +122,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); @@ -125,6 +143,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); @@ -143,6 +164,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlaySrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); @@ -161,6 +185,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); @@ -179,6 +206,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); @@ -197,6 +227,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplySrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); @@ -215,6 +248,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); @@ -233,6 +269,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); @@ -251,6 +290,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); @@ -269,6 +311,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); @@ -287,6 +332,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); @@ -305,6 +353,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlaySrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); @@ -323,6 +374,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); @@ -341,6 +395,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); @@ -359,6 +416,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplySrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); @@ -377,6 +437,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); @@ -395,6 +458,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); @@ -413,6 +479,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); @@ -431,6 +500,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); @@ -449,6 +521,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); @@ -467,6 +542,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlaySrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); @@ -485,6 +563,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); @@ -503,6 +584,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); @@ -521,6 +605,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplySrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); @@ -539,6 +626,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); @@ -557,6 +647,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); @@ -575,6 +668,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); @@ -593,6 +689,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); @@ -611,6 +710,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); @@ -629,6 +731,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlaySrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); @@ -647,6 +752,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); @@ -665,6 +773,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); @@ -683,6 +794,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplySrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); @@ -701,6 +815,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); @@ -719,6 +836,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); @@ -737,6 +857,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); @@ -755,6 +878,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); @@ -773,6 +899,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); @@ -791,6 +920,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlaySrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); @@ -809,6 +941,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); @@ -827,6 +962,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); @@ -845,6 +983,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); @@ -863,6 +1004,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); @@ -881,6 +1025,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); @@ -899,6 +1046,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); @@ -917,6 +1067,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); @@ -935,6 +1088,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); @@ -953,6 +1109,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); @@ -971,6 +1130,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); @@ -989,6 +1151,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); @@ -1007,6 +1172,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); @@ -1025,6 +1193,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); @@ -1043,6 +1214,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); @@ -1061,6 +1235,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); @@ -1079,6 +1256,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); @@ -1097,6 +1277,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); @@ -1115,6 +1298,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); @@ -1133,6 +1319,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); @@ -1151,6 +1340,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); @@ -1169,6 +1361,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); @@ -1187,6 +1382,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); @@ -1205,6 +1403,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); @@ -1223,6 +1424,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); @@ -1241,6 +1445,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); @@ -1259,6 +1466,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); @@ -1277,6 +1487,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); @@ -1295,6 +1508,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); @@ -1313,6 +1529,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); @@ -1331,6 +1550,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); @@ -1349,6 +1571,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); @@ -1367,6 +1592,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); @@ -1385,6 +1613,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); @@ -1403,6 +1634,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); @@ -1421,6 +1655,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); @@ -1439,6 +1676,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); @@ -1457,6 +1697,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); @@ -1475,6 +1718,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); @@ -1493,6 +1739,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); @@ -1511,6 +1760,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); @@ -1529,6 +1781,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); @@ -1547,6 +1802,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); @@ -1565,6 +1823,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); @@ -1583,6 +1844,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); @@ -1601,6 +1865,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); @@ -1619,6 +1886,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); @@ -1637,6 +1907,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); @@ -1655,6 +1928,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); @@ -1673,6 +1949,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); @@ -1691,6 +1970,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); @@ -1709,6 +1991,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); @@ -1727,6 +2012,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); @@ -1745,6 +2033,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); @@ -1763,6 +2054,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); @@ -1781,6 +2075,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); @@ -1799,6 +2096,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct NormalXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); @@ -1817,6 +2117,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct MultiplyXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); @@ -1835,6 +2138,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct AddXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); @@ -1853,6 +2159,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct SubtractXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); @@ -1871,6 +2180,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct ScreenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); @@ -1889,6 +2201,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct DarkenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); @@ -1907,6 +2222,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct LightenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); @@ -1925,6 +2243,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct OverlayXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); @@ -1943,6 +2264,9 @@ internal static class AssociatedAlphaPixelBlenderOperators /// public readonly struct HardLightXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); @@ -1966,1299 +2290,543 @@ internal static partial class AssociatedAlphaPixelBlenders where TPixel : unmanaged, IPixel { /// - /// Blends pixels with the "NormalSrc" composition equation. + /// Gets the shared blender for the "NormalSrc" composition equation. /// - public sealed class NormalSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrc Instance { get; } = new(); - } + public static PixelBlender NormalSrc => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrc" composition equation. + /// Gets the shared blender for the "MultiplySrc" composition equation. /// - public sealed class MultiplySrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrc Instance { get; } = new(); - } + public static PixelBlender MultiplySrc => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrc" composition equation. + /// Gets the shared blender for the "AddSrc" composition equation. /// - public sealed class AddSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrc Instance { get; } = new(); - } + public static PixelBlender AddSrc => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrc" composition equation. + /// Gets the shared blender for the "SubtractSrc" composition equation. /// - public sealed class SubtractSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrc Instance { get; } = new(); - } + public static PixelBlender SubtractSrc => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrc" composition equation. + /// Gets the shared blender for the "ScreenSrc" composition equation. /// - public sealed class ScreenSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrc Instance { get; } = new(); - } + public static PixelBlender ScreenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrc" composition equation. + /// Gets the shared blender for the "DarkenSrc" composition equation. /// - public sealed class DarkenSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrc Instance { get; } = new(); - } + public static PixelBlender DarkenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrc" composition equation. + /// Gets the shared blender for the "LightenSrc" composition equation. /// - public sealed class LightenSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrc Instance { get; } = new(); - } + public static PixelBlender LightenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrc" composition equation. + /// Gets the shared blender for the "OverlaySrc" composition equation. /// - public sealed class OverlaySrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrc Instance { get; } = new(); - } + public static PixelBlender OverlaySrc => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrc" composition equation. + /// Gets the shared blender for the "HardLightSrc" composition equation. /// - public sealed class HardLightSrc : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrc Instance { get; } = new(); - } + public static PixelBlender HardLightSrc => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcAtop" composition equation. + /// Gets the shared blender for the "NormalSrcAtop" composition equation. /// - public sealed class NormalSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcAtop Instance { get; } = new(); - } + public static PixelBlender NormalSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcAtop" composition equation. + /// Gets the shared blender for the "MultiplySrcAtop" composition equation. /// - public sealed class MultiplySrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcAtop Instance { get; } = new(); - } + public static PixelBlender MultiplySrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcAtop" composition equation. + /// Gets the shared blender for the "AddSrcAtop" composition equation. /// - public sealed class AddSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcAtop Instance { get; } = new(); - } + public static PixelBlender AddSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcAtop" composition equation. + /// Gets the shared blender for the "SubtractSrcAtop" composition equation. /// - public sealed class SubtractSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcAtop Instance { get; } = new(); - } + public static PixelBlender SubtractSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcAtop" composition equation. + /// Gets the shared blender for the "ScreenSrcAtop" composition equation. /// - public sealed class ScreenSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcAtop Instance { get; } = new(); - } + public static PixelBlender ScreenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcAtop" composition equation. + /// Gets the shared blender for the "DarkenSrcAtop" composition equation. /// - public sealed class DarkenSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcAtop Instance { get; } = new(); - } + public static PixelBlender DarkenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcAtop" composition equation. + /// Gets the shared blender for the "LightenSrcAtop" composition equation. /// - public sealed class LightenSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcAtop Instance { get; } = new(); - } + public static PixelBlender LightenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcAtop" composition equation. + /// Gets the shared blender for the "OverlaySrcAtop" composition equation. /// - public sealed class OverlaySrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcAtop Instance { get; } = new(); - } + public static PixelBlender OverlaySrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcAtop" composition equation. + /// Gets the shared blender for the "HardLightSrcAtop" composition equation. /// - public sealed class HardLightSrcAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcAtop Instance { get; } = new(); - } + public static PixelBlender HardLightSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcOver" composition equation. + /// Gets the shared blender for the "NormalSrcOver" composition equation. /// - public sealed class NormalSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcOver Instance { get; } = new(); - } + public static PixelBlender NormalSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcOver" composition equation. + /// Gets the shared blender for the "MultiplySrcOver" composition equation. /// - public sealed class MultiplySrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcOver Instance { get; } = new(); - } + public static PixelBlender MultiplySrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcOver" composition equation. + /// Gets the shared blender for the "AddSrcOver" composition equation. /// - public sealed class AddSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcOver Instance { get; } = new(); - } + public static PixelBlender AddSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcOver" composition equation. + /// Gets the shared blender for the "SubtractSrcOver" composition equation. /// - public sealed class SubtractSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcOver Instance { get; } = new(); - } + public static PixelBlender SubtractSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcOver" composition equation. + /// Gets the shared blender for the "ScreenSrcOver" composition equation. /// - public sealed class ScreenSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcOver Instance { get; } = new(); - } + public static PixelBlender ScreenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcOver" composition equation. + /// Gets the shared blender for the "DarkenSrcOver" composition equation. /// - public sealed class DarkenSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcOver Instance { get; } = new(); - } + public static PixelBlender DarkenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcOver" composition equation. + /// Gets the shared blender for the "LightenSrcOver" composition equation. /// - public sealed class LightenSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcOver Instance { get; } = new(); - } + public static PixelBlender LightenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcOver" composition equation. + /// Gets the shared blender for the "OverlaySrcOver" composition equation. /// - public sealed class OverlaySrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcOver Instance { get; } = new(); - } + public static PixelBlender OverlaySrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcOver" composition equation. + /// Gets the shared blender for the "HardLightSrcOver" composition equation. /// - public sealed class HardLightSrcOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcOver Instance { get; } = new(); - } + public static PixelBlender HardLightSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcIn" composition equation. + /// Gets the shared blender for the "NormalSrcIn" composition equation. /// - public sealed class NormalSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcIn Instance { get; } = new(); - } + public static PixelBlender NormalSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcIn" composition equation. + /// Gets the shared blender for the "MultiplySrcIn" composition equation. /// - public sealed class MultiplySrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcIn Instance { get; } = new(); - } + public static PixelBlender MultiplySrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcIn" composition equation. + /// Gets the shared blender for the "AddSrcIn" composition equation. /// - public sealed class AddSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcIn Instance { get; } = new(); - } + public static PixelBlender AddSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcIn" composition equation. + /// Gets the shared blender for the "SubtractSrcIn" composition equation. /// - public sealed class SubtractSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcIn Instance { get; } = new(); - } + public static PixelBlender SubtractSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcIn" composition equation. + /// Gets the shared blender for the "ScreenSrcIn" composition equation. /// - public sealed class ScreenSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcIn Instance { get; } = new(); - } + public static PixelBlender ScreenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcIn" composition equation. + /// Gets the shared blender for the "DarkenSrcIn" composition equation. /// - public sealed class DarkenSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcIn Instance { get; } = new(); - } + public static PixelBlender DarkenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcIn" composition equation. + /// Gets the shared blender for the "LightenSrcIn" composition equation. /// - public sealed class LightenSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcIn Instance { get; } = new(); - } + public static PixelBlender LightenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcIn" composition equation. + /// Gets the shared blender for the "OverlaySrcIn" composition equation. /// - public sealed class OverlaySrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcIn Instance { get; } = new(); - } + public static PixelBlender OverlaySrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcIn" composition equation. + /// Gets the shared blender for the "HardLightSrcIn" composition equation. /// - public sealed class HardLightSrcIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcIn Instance { get; } = new(); - } + public static PixelBlender HardLightSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcOut" composition equation. + /// Gets the shared blender for the "NormalSrcOut" composition equation. /// - public sealed class NormalSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcOut Instance { get; } = new(); - } + public static PixelBlender NormalSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcOut" composition equation. + /// Gets the shared blender for the "MultiplySrcOut" composition equation. /// - public sealed class MultiplySrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcOut Instance { get; } = new(); - } + public static PixelBlender MultiplySrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcOut" composition equation. + /// Gets the shared blender for the "AddSrcOut" composition equation. /// - public sealed class AddSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcOut Instance { get; } = new(); - } + public static PixelBlender AddSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcOut" composition equation. + /// Gets the shared blender for the "SubtractSrcOut" composition equation. /// - public sealed class SubtractSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcOut Instance { get; } = new(); - } + public static PixelBlender SubtractSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcOut" composition equation. + /// Gets the shared blender for the "ScreenSrcOut" composition equation. /// - public sealed class ScreenSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcOut Instance { get; } = new(); - } + public static PixelBlender ScreenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcOut" composition equation. + /// Gets the shared blender for the "DarkenSrcOut" composition equation. /// - public sealed class DarkenSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcOut Instance { get; } = new(); - } + public static PixelBlender DarkenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcOut" composition equation. + /// Gets the shared blender for the "LightenSrcOut" composition equation. /// - public sealed class LightenSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcOut Instance { get; } = new(); - } + public static PixelBlender LightenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcOut" composition equation. + /// Gets the shared blender for the "OverlaySrcOut" composition equation. /// - public sealed class OverlaySrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcOut Instance { get; } = new(); - } + public static PixelBlender OverlaySrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcOut" composition equation. + /// Gets the shared blender for the "HardLightSrcOut" composition equation. /// - public sealed class HardLightSrcOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcOut Instance { get; } = new(); - } + public static PixelBlender HardLightSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDest" composition equation. + /// Gets the shared blender for the "NormalDest" composition equation. /// - public sealed class NormalDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDest Instance { get; } = new(); - } + public static PixelBlender NormalDest => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDest" composition equation. + /// Gets the shared blender for the "MultiplyDest" composition equation. /// - public sealed class MultiplyDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDest Instance { get; } = new(); - } + public static PixelBlender MultiplyDest => PixelBlender.Instance; /// - /// Blends pixels with the "AddDest" composition equation. + /// Gets the shared blender for the "AddDest" composition equation. /// - public sealed class AddDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDest Instance { get; } = new(); - } + public static PixelBlender AddDest => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDest" composition equation. + /// Gets the shared blender for the "SubtractDest" composition equation. /// - public sealed class SubtractDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDest Instance { get; } = new(); - } + public static PixelBlender SubtractDest => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDest" composition equation. + /// Gets the shared blender for the "ScreenDest" composition equation. /// - public sealed class ScreenDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDest Instance { get; } = new(); - } + public static PixelBlender ScreenDest => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDest" composition equation. + /// Gets the shared blender for the "DarkenDest" composition equation. /// - public sealed class DarkenDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDest Instance { get; } = new(); - } + public static PixelBlender DarkenDest => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDest" composition equation. + /// Gets the shared blender for the "LightenDest" composition equation. /// - public sealed class LightenDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDest Instance { get; } = new(); - } + public static PixelBlender LightenDest => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDest" composition equation. + /// Gets the shared blender for the "OverlayDest" composition equation. /// - public sealed class OverlayDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDest Instance { get; } = new(); - } + public static PixelBlender OverlayDest => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDest" composition equation. + /// Gets the shared blender for the "HardLightDest" composition equation. /// - public sealed class HardLightDest : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDest Instance { get; } = new(); - } + public static PixelBlender HardLightDest => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestAtop" composition equation. + /// Gets the shared blender for the "NormalDestAtop" composition equation. /// - public sealed class NormalDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestAtop Instance { get; } = new(); - } + public static PixelBlender NormalDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestAtop" composition equation. + /// Gets the shared blender for the "MultiplyDestAtop" composition equation. /// - public sealed class MultiplyDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestAtop Instance { get; } = new(); - } + public static PixelBlender MultiplyDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestAtop" composition equation. + /// Gets the shared blender for the "AddDestAtop" composition equation. /// - public sealed class AddDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestAtop Instance { get; } = new(); - } + public static PixelBlender AddDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestAtop" composition equation. + /// Gets the shared blender for the "SubtractDestAtop" composition equation. /// - public sealed class SubtractDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestAtop Instance { get; } = new(); - } + public static PixelBlender SubtractDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestAtop" composition equation. + /// Gets the shared blender for the "ScreenDestAtop" composition equation. /// - public sealed class ScreenDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestAtop Instance { get; } = new(); - } + public static PixelBlender ScreenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestAtop" composition equation. + /// Gets the shared blender for the "DarkenDestAtop" composition equation. /// - public sealed class DarkenDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestAtop Instance { get; } = new(); - } + public static PixelBlender DarkenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestAtop" composition equation. + /// Gets the shared blender for the "LightenDestAtop" composition equation. /// - public sealed class LightenDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestAtop Instance { get; } = new(); - } + public static PixelBlender LightenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestAtop" composition equation. + /// Gets the shared blender for the "OverlayDestAtop" composition equation. /// - public sealed class OverlayDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestAtop Instance { get; } = new(); - } + public static PixelBlender OverlayDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestAtop" composition equation. + /// Gets the shared blender for the "HardLightDestAtop" composition equation. /// - public sealed class HardLightDestAtop : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestAtop Instance { get; } = new(); - } + public static PixelBlender HardLightDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestOver" composition equation. + /// Gets the shared blender for the "NormalDestOver" composition equation. /// - public sealed class NormalDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestOver Instance { get; } = new(); - } + public static PixelBlender NormalDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestOver" composition equation. + /// Gets the shared blender for the "MultiplyDestOver" composition equation. /// - public sealed class MultiplyDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestOver Instance { get; } = new(); - } + public static PixelBlender MultiplyDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestOver" composition equation. + /// Gets the shared blender for the "AddDestOver" composition equation. /// - public sealed class AddDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestOver Instance { get; } = new(); - } + public static PixelBlender AddDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestOver" composition equation. + /// Gets the shared blender for the "SubtractDestOver" composition equation. /// - public sealed class SubtractDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestOver Instance { get; } = new(); - } + public static PixelBlender SubtractDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestOver" composition equation. + /// Gets the shared blender for the "ScreenDestOver" composition equation. /// - public sealed class ScreenDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestOver Instance { get; } = new(); - } + public static PixelBlender ScreenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestOver" composition equation. + /// Gets the shared blender for the "DarkenDestOver" composition equation. /// - public sealed class DarkenDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestOver Instance { get; } = new(); - } + public static PixelBlender DarkenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestOver" composition equation. + /// Gets the shared blender for the "LightenDestOver" composition equation. /// - public sealed class LightenDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestOver Instance { get; } = new(); - } + public static PixelBlender LightenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestOver" composition equation. + /// Gets the shared blender for the "OverlayDestOver" composition equation. /// - public sealed class OverlayDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestOver Instance { get; } = new(); - } + public static PixelBlender OverlayDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestOver" composition equation. + /// Gets the shared blender for the "HardLightDestOver" composition equation. /// - public sealed class HardLightDestOver : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestOver Instance { get; } = new(); - } + public static PixelBlender HardLightDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestIn" composition equation. + /// Gets the shared blender for the "NormalDestIn" composition equation. /// - public sealed class NormalDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestIn Instance { get; } = new(); - } + public static PixelBlender NormalDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestIn" composition equation. + /// Gets the shared blender for the "MultiplyDestIn" composition equation. /// - public sealed class MultiplyDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestIn Instance { get; } = new(); - } + public static PixelBlender MultiplyDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestIn" composition equation. + /// Gets the shared blender for the "AddDestIn" composition equation. /// - public sealed class AddDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestIn Instance { get; } = new(); - } + public static PixelBlender AddDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestIn" composition equation. + /// Gets the shared blender for the "SubtractDestIn" composition equation. /// - public sealed class SubtractDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestIn Instance { get; } = new(); - } + public static PixelBlender SubtractDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestIn" composition equation. + /// Gets the shared blender for the "ScreenDestIn" composition equation. /// - public sealed class ScreenDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestIn Instance { get; } = new(); - } + public static PixelBlender ScreenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestIn" composition equation. + /// Gets the shared blender for the "DarkenDestIn" composition equation. /// - public sealed class DarkenDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestIn Instance { get; } = new(); - } + public static PixelBlender DarkenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestIn" composition equation. + /// Gets the shared blender for the "LightenDestIn" composition equation. /// - public sealed class LightenDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestIn Instance { get; } = new(); - } + public static PixelBlender LightenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestIn" composition equation. + /// Gets the shared blender for the "OverlayDestIn" composition equation. /// - public sealed class OverlayDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestIn Instance { get; } = new(); - } + public static PixelBlender OverlayDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestIn" composition equation. + /// Gets the shared blender for the "HardLightDestIn" composition equation. /// - public sealed class HardLightDestIn : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestIn Instance { get; } = new(); - } + public static PixelBlender HardLightDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestOut" composition equation. + /// Gets the shared blender for the "NormalDestOut" composition equation. /// - public sealed class NormalDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestOut Instance { get; } = new(); - } + public static PixelBlender NormalDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestOut" composition equation. + /// Gets the shared blender for the "MultiplyDestOut" composition equation. /// - public sealed class MultiplyDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestOut Instance { get; } = new(); - } + public static PixelBlender MultiplyDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestOut" composition equation. + /// Gets the shared blender for the "AddDestOut" composition equation. /// - public sealed class AddDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestOut Instance { get; } = new(); - } + public static PixelBlender AddDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestOut" composition equation. + /// Gets the shared blender for the "SubtractDestOut" composition equation. /// - public sealed class SubtractDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestOut Instance { get; } = new(); - } + public static PixelBlender SubtractDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestOut" composition equation. + /// Gets the shared blender for the "ScreenDestOut" composition equation. /// - public sealed class ScreenDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestOut Instance { get; } = new(); - } + public static PixelBlender ScreenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestOut" composition equation. + /// Gets the shared blender for the "DarkenDestOut" composition equation. /// - public sealed class DarkenDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestOut Instance { get; } = new(); - } + public static PixelBlender DarkenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestOut" composition equation. + /// Gets the shared blender for the "LightenDestOut" composition equation. /// - public sealed class LightenDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestOut Instance { get; } = new(); - } + public static PixelBlender LightenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestOut" composition equation. + /// Gets the shared blender for the "OverlayDestOut" composition equation. /// - public sealed class OverlayDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestOut Instance { get; } = new(); - } + public static PixelBlender OverlayDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestOut" composition equation. + /// Gets the shared blender for the "HardLightDestOut" composition equation. /// - public sealed class HardLightDestOut : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestOut Instance { get; } = new(); - } + public static PixelBlender HardLightDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "NormalClear" composition equation. + /// Gets the shared blender for the "NormalClear" composition equation. /// - public sealed class NormalClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalClear Instance { get; } = new(); - } + public static PixelBlender NormalClear => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyClear" composition equation. + /// Gets the shared blender for the "MultiplyClear" composition equation. /// - public sealed class MultiplyClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyClear Instance { get; } = new(); - } + public static PixelBlender MultiplyClear => PixelBlender.Instance; /// - /// Blends pixels with the "AddClear" composition equation. + /// Gets the shared blender for the "AddClear" composition equation. /// - public sealed class AddClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddClear Instance { get; } = new(); - } + public static PixelBlender AddClear => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractClear" composition equation. + /// Gets the shared blender for the "SubtractClear" composition equation. /// - public sealed class SubtractClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractClear Instance { get; } = new(); - } + public static PixelBlender SubtractClear => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenClear" composition equation. + /// Gets the shared blender for the "ScreenClear" composition equation. /// - public sealed class ScreenClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenClear Instance { get; } = new(); - } + public static PixelBlender ScreenClear => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenClear" composition equation. + /// Gets the shared blender for the "DarkenClear" composition equation. /// - public sealed class DarkenClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenClear Instance { get; } = new(); - } + public static PixelBlender DarkenClear => PixelBlender.Instance; /// - /// Blends pixels with the "LightenClear" composition equation. + /// Gets the shared blender for the "LightenClear" composition equation. /// - public sealed class LightenClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenClear Instance { get; } = new(); - } + public static PixelBlender LightenClear => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayClear" composition equation. + /// Gets the shared blender for the "OverlayClear" composition equation. /// - public sealed class OverlayClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayClear Instance { get; } = new(); - } + public static PixelBlender OverlayClear => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightClear" composition equation. + /// Gets the shared blender for the "HardLightClear" composition equation. /// - public sealed class HardLightClear : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightClear Instance { get; } = new(); - } + public static PixelBlender HardLightClear => PixelBlender.Instance; /// - /// Blends pixels with the "NormalXor" composition equation. + /// Gets the shared blender for the "NormalXor" composition equation. /// - public sealed class NormalXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalXor Instance { get; } = new(); - } + public static PixelBlender NormalXor => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyXor" composition equation. + /// Gets the shared blender for the "MultiplyXor" composition equation. /// - public sealed class MultiplyXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyXor Instance { get; } = new(); - } + public static PixelBlender MultiplyXor => PixelBlender.Instance; /// - /// Blends pixels with the "AddXor" composition equation. + /// Gets the shared blender for the "AddXor" composition equation. /// - public sealed class AddXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddXor Instance { get; } = new(); - } + public static PixelBlender AddXor => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractXor" composition equation. + /// Gets the shared blender for the "SubtractXor" composition equation. /// - public sealed class SubtractXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractXor Instance { get; } = new(); - } + public static PixelBlender SubtractXor => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenXor" composition equation. + /// Gets the shared blender for the "ScreenXor" composition equation. /// - public sealed class ScreenXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenXor Instance { get; } = new(); - } + public static PixelBlender ScreenXor => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenXor" composition equation. + /// Gets the shared blender for the "DarkenXor" composition equation. /// - public sealed class DarkenXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenXor Instance { get; } = new(); - } + public static PixelBlender DarkenXor => PixelBlender.Instance; /// - /// Blends pixels with the "LightenXor" composition equation. + /// Gets the shared blender for the "LightenXor" composition equation. /// - public sealed class LightenXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenXor Instance { get; } = new(); - } + public static PixelBlender LightenXor => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayXor" composition equation. + /// Gets the shared blender for the "OverlayXor" composition equation. /// - public sealed class OverlayXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayXor Instance { get; } = new(); - } + public static PixelBlender OverlayXor => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightXor" composition equation. + /// Gets the shared blender for the "HardLightXor" composition equation. /// - public sealed class HardLightXor : - AssociatedAlphaPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightXor Instance { get; } = new(); - } + public static PixelBlender HardLightXor => PixelBlender.Instance; } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt index 801d76328..5fc7f91c4 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt @@ -61,6 +61,9 @@ foreach (var composer in composers) /// public readonly struct <#= blenderComposer #> : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => true; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); @@ -95,16 +98,9 @@ foreach (var composer in composers) var blenderComposer = $"{blender}{composer}"; #> /// - /// Blends pixels with the "<#= blenderComposer #>" composition equation. + /// Gets the shared blender for the "<#= blenderComposer #>" composition equation. /// - public sealed class <#= blenderComposer #> : - AssociatedAlphaPixelBlender> - { - /// - /// Gets the shared blender instance. - /// - public static <#= blenderComposer #> Instance { get; } = new(); - } + public static PixelBlender <#= blenderComposer #> => PixelBlender>.Instance; <# } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs index 6ce1235b4..0664e3fa0 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs @@ -21,147 +21,147 @@ internal static partial class AssociatedAlphaPixelBlenders { 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, + PixelColorBlendingMode.Multiply => MultiplySrc, + PixelColorBlendingMode.Add => AddSrc, + PixelColorBlendingMode.Subtract => SubtractSrc, + PixelColorBlendingMode.Screen => ScreenSrc, + PixelColorBlendingMode.Darken => DarkenSrc, + PixelColorBlendingMode.Lighten => LightenSrc, + PixelColorBlendingMode.Overlay => OverlaySrc, + PixelColorBlendingMode.HardLight => HardLightSrc, + _ => NormalSrc, }, 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, + PixelColorBlendingMode.Multiply => MultiplySrcAtop, + PixelColorBlendingMode.Add => AddSrcAtop, + PixelColorBlendingMode.Subtract => SubtractSrcAtop, + PixelColorBlendingMode.Screen => ScreenSrcAtop, + PixelColorBlendingMode.Darken => DarkenSrcAtop, + PixelColorBlendingMode.Lighten => LightenSrcAtop, + PixelColorBlendingMode.Overlay => OverlaySrcAtop, + PixelColorBlendingMode.HardLight => HardLightSrcAtop, + _ => NormalSrcAtop, }, 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, + PixelColorBlendingMode.Multiply => MultiplySrcIn, + PixelColorBlendingMode.Add => AddSrcIn, + PixelColorBlendingMode.Subtract => SubtractSrcIn, + PixelColorBlendingMode.Screen => ScreenSrcIn, + PixelColorBlendingMode.Darken => DarkenSrcIn, + PixelColorBlendingMode.Lighten => LightenSrcIn, + PixelColorBlendingMode.Overlay => OverlaySrcIn, + PixelColorBlendingMode.HardLight => HardLightSrcIn, + _ => NormalSrcIn, }, 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, + PixelColorBlendingMode.Multiply => MultiplySrcOut, + PixelColorBlendingMode.Add => AddSrcOut, + PixelColorBlendingMode.Subtract => SubtractSrcOut, + PixelColorBlendingMode.Screen => ScreenSrcOut, + PixelColorBlendingMode.Darken => DarkenSrcOut, + PixelColorBlendingMode.Lighten => LightenSrcOut, + PixelColorBlendingMode.Overlay => OverlaySrcOut, + PixelColorBlendingMode.HardLight => HardLightSrcOut, + _ => NormalSrcOut, }, 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, + PixelColorBlendingMode.Multiply => MultiplyDest, + PixelColorBlendingMode.Add => AddDest, + PixelColorBlendingMode.Subtract => SubtractDest, + PixelColorBlendingMode.Screen => ScreenDest, + PixelColorBlendingMode.Darken => DarkenDest, + PixelColorBlendingMode.Lighten => LightenDest, + PixelColorBlendingMode.Overlay => OverlayDest, + PixelColorBlendingMode.HardLight => HardLightDest, + _ => NormalDest, }, 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, + PixelColorBlendingMode.Multiply => MultiplyDestAtop, + PixelColorBlendingMode.Add => AddDestAtop, + PixelColorBlendingMode.Subtract => SubtractDestAtop, + PixelColorBlendingMode.Screen => ScreenDestAtop, + PixelColorBlendingMode.Darken => DarkenDestAtop, + PixelColorBlendingMode.Lighten => LightenDestAtop, + PixelColorBlendingMode.Overlay => OverlayDestAtop, + PixelColorBlendingMode.HardLight => HardLightDestAtop, + _ => NormalDestAtop, }, 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, + PixelColorBlendingMode.Multiply => MultiplyDestOver, + PixelColorBlendingMode.Add => AddDestOver, + PixelColorBlendingMode.Subtract => SubtractDestOver, + PixelColorBlendingMode.Screen => ScreenDestOver, + PixelColorBlendingMode.Darken => DarkenDestOver, + PixelColorBlendingMode.Lighten => LightenDestOver, + PixelColorBlendingMode.Overlay => OverlayDestOver, + PixelColorBlendingMode.HardLight => HardLightDestOver, + _ => NormalDestOver, }, 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, + PixelColorBlendingMode.Multiply => MultiplyDestIn, + PixelColorBlendingMode.Add => AddDestIn, + PixelColorBlendingMode.Subtract => SubtractDestIn, + PixelColorBlendingMode.Screen => ScreenDestIn, + PixelColorBlendingMode.Darken => DarkenDestIn, + PixelColorBlendingMode.Lighten => LightenDestIn, + PixelColorBlendingMode.Overlay => OverlayDestIn, + PixelColorBlendingMode.HardLight => HardLightDestIn, + _ => NormalDestIn, }, 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, + PixelColorBlendingMode.Multiply => MultiplyDestOut, + PixelColorBlendingMode.Add => AddDestOut, + PixelColorBlendingMode.Subtract => SubtractDestOut, + PixelColorBlendingMode.Screen => ScreenDestOut, + PixelColorBlendingMode.Darken => DarkenDestOut, + PixelColorBlendingMode.Lighten => LightenDestOut, + PixelColorBlendingMode.Overlay => OverlayDestOut, + PixelColorBlendingMode.HardLight => HardLightDestOut, + _ => NormalDestOut, }, 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, + PixelColorBlendingMode.Multiply => MultiplyClear, + PixelColorBlendingMode.Add => AddClear, + PixelColorBlendingMode.Subtract => SubtractClear, + PixelColorBlendingMode.Screen => ScreenClear, + PixelColorBlendingMode.Darken => DarkenClear, + PixelColorBlendingMode.Lighten => LightenClear, + PixelColorBlendingMode.Overlay => OverlayClear, + PixelColorBlendingMode.HardLight => HardLightClear, + _ => NormalClear, }, 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, + PixelColorBlendingMode.Multiply => MultiplyXor, + PixelColorBlendingMode.Add => AddXor, + PixelColorBlendingMode.Subtract => SubtractXor, + PixelColorBlendingMode.Screen => ScreenXor, + PixelColorBlendingMode.Darken => DarkenXor, + PixelColorBlendingMode.Lighten => LightenXor, + PixelColorBlendingMode.Overlay => OverlayXor, + PixelColorBlendingMode.HardLight => HardLightXor, + _ => NormalXor, }, _ => 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, + PixelColorBlendingMode.Multiply => MultiplySrcOver, + PixelColorBlendingMode.Add => AddSrcOver, + PixelColorBlendingMode.Subtract => SubtractSrcOver, + PixelColorBlendingMode.Screen => ScreenSrcOver, + PixelColorBlendingMode.Darken => DarkenSrcOver, + PixelColorBlendingMode.Lighten => LightenSrcOver, + PixelColorBlendingMode.Overlay => OverlaySrcOver, + PixelColorBlendingMode.HardLight => HardLightSrcOver, + _ => NormalSrcOver, }, }; } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs deleted file mode 100644 index 81a3f24a8..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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. -/// The Porter-Duff equation. -internal abstract class AssociatedAlphaPixelBlender : PixelBlender - where TPixel : unmanaged, IPixel - where TOperator : struct, IPixelBlenderOperator -{ - private static readonly PixelOperations Operations = PixelOperations.Instance; - - /// - public sealed override TPixel Blend(TPixel background, TPixel source, float amount) - { - // Associated RGB and alpha must remain in their stored representation throughout composition. - Vector4 result = TOperator.Invoke(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1F)); - - return TPixel.FromAssociatedScaledVector4(result); - } - - /// - 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/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 2ff4660b7..298c35999 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -17,6 +17,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalSrc(background, source, amount); @@ -35,6 +38,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplySrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplySrc(background, source, amount); @@ -53,6 +59,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddSrc(background, source, amount); @@ -71,6 +80,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractSrc(background, source, amount); @@ -89,6 +101,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenSrc(background, source, amount); @@ -107,6 +122,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenSrc(background, source, amount); @@ -125,6 +143,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenSrc(background, source, amount); @@ -143,6 +164,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlaySrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlaySrc(background, source, amount); @@ -161,6 +185,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightSrc : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightSrc(background, source, amount); @@ -179,6 +206,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalSrcAtop(background, source, amount); @@ -197,6 +227,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplySrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); @@ -215,6 +248,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddSrcAtop(background, source, amount); @@ -233,6 +269,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); @@ -251,6 +290,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); @@ -269,6 +311,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); @@ -287,6 +332,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenSrcAtop(background, source, amount); @@ -305,6 +353,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlaySrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); @@ -323,6 +374,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightSrcAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); @@ -341,6 +395,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalSrcOver(background, source, amount); @@ -359,6 +416,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplySrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplySrcOver(background, source, amount); @@ -377,6 +437,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddSrcOver(background, source, amount); @@ -395,6 +458,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractSrcOver(background, source, amount); @@ -413,6 +479,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenSrcOver(background, source, amount); @@ -431,6 +500,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenSrcOver(background, source, amount); @@ -449,6 +521,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenSrcOver(background, source, amount); @@ -467,6 +542,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlaySrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlaySrcOver(background, source, amount); @@ -485,6 +563,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightSrcOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightSrcOver(background, source, amount); @@ -503,6 +584,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalSrcIn(background, source, amount); @@ -521,6 +605,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplySrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplySrcIn(background, source, amount); @@ -539,6 +626,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddSrcIn(background, source, amount); @@ -557,6 +647,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractSrcIn(background, source, amount); @@ -575,6 +668,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenSrcIn(background, source, amount); @@ -593,6 +689,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenSrcIn(background, source, amount); @@ -611,6 +710,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenSrcIn(background, source, amount); @@ -629,6 +731,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlaySrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlaySrcIn(background, source, amount); @@ -647,6 +752,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightSrcIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightSrcIn(background, source, amount); @@ -665,6 +773,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalSrcOut(background, source, amount); @@ -683,6 +794,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplySrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplySrcOut(background, source, amount); @@ -701,6 +815,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddSrcOut(background, source, amount); @@ -719,6 +836,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractSrcOut(background, source, amount); @@ -737,6 +857,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenSrcOut(background, source, amount); @@ -755,6 +878,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenSrcOut(background, source, amount); @@ -773,6 +899,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenSrcOut(background, source, amount); @@ -791,6 +920,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlaySrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlaySrcOut(background, source, amount); @@ -809,6 +941,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightSrcOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightSrcOut(background, source, amount); @@ -827,6 +962,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalDest(background, source, amount); @@ -845,6 +983,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyDest(background, source, amount); @@ -863,6 +1004,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddDest(background, source, amount); @@ -881,6 +1025,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractDest(background, source, amount); @@ -899,6 +1046,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenDest(background, source, amount); @@ -917,6 +1067,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenDest(background, source, amount); @@ -935,6 +1088,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenDest(background, source, amount); @@ -953,6 +1109,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayDest(background, source, amount); @@ -971,6 +1130,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightDest : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightDest(background, source, amount); @@ -989,6 +1151,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalDestAtop(background, source, amount); @@ -1007,6 +1172,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); @@ -1025,6 +1193,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddDestAtop(background, source, amount); @@ -1043,6 +1214,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractDestAtop(background, source, amount); @@ -1061,6 +1235,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenDestAtop(background, source, amount); @@ -1079,6 +1256,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenDestAtop(background, source, amount); @@ -1097,6 +1277,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenDestAtop(background, source, amount); @@ -1115,6 +1298,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayDestAtop(background, source, amount); @@ -1133,6 +1319,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightDestAtop : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightDestAtop(background, source, amount); @@ -1151,6 +1340,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalDestOver(background, source, amount); @@ -1169,6 +1361,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyDestOver(background, source, amount); @@ -1187,6 +1382,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddDestOver(background, source, amount); @@ -1205,6 +1403,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractDestOver(background, source, amount); @@ -1223,6 +1424,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenDestOver(background, source, amount); @@ -1241,6 +1445,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenDestOver(background, source, amount); @@ -1259,6 +1466,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenDestOver(background, source, amount); @@ -1277,6 +1487,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayDestOver(background, source, amount); @@ -1295,6 +1508,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightDestOver : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightDestOver(background, source, amount); @@ -1313,6 +1529,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalDestIn(background, source, amount); @@ -1331,6 +1550,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyDestIn(background, source, amount); @@ -1349,6 +1571,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddDestIn(background, source, amount); @@ -1367,6 +1592,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractDestIn(background, source, amount); @@ -1385,6 +1613,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenDestIn(background, source, amount); @@ -1403,6 +1634,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenDestIn(background, source, amount); @@ -1421,6 +1655,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenDestIn(background, source, amount); @@ -1439,6 +1676,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayDestIn(background, source, amount); @@ -1457,6 +1697,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightDestIn : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightDestIn(background, source, amount); @@ -1475,6 +1718,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalDestOut(background, source, amount); @@ -1493,6 +1739,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyDestOut(background, source, amount); @@ -1511,6 +1760,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddDestOut(background, source, amount); @@ -1529,6 +1781,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractDestOut(background, source, amount); @@ -1547,6 +1802,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenDestOut(background, source, amount); @@ -1565,6 +1823,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenDestOut(background, source, amount); @@ -1583,6 +1844,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenDestOut(background, source, amount); @@ -1601,6 +1865,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayDestOut(background, source, amount); @@ -1619,6 +1886,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightDestOut : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightDestOut(background, source, amount); @@ -1637,6 +1907,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalClear(background, source, amount); @@ -1655,6 +1928,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyClear(background, source, amount); @@ -1673,6 +1949,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddClear(background, source, amount); @@ -1691,6 +1970,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractClear(background, source, amount); @@ -1709,6 +1991,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenClear(background, source, amount); @@ -1727,6 +2012,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenClear(background, source, amount); @@ -1745,6 +2033,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenClear(background, source, amount); @@ -1763,6 +2054,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayClear(background, source, amount); @@ -1781,6 +2075,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightClear : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightClear(background, source, amount); @@ -1799,6 +2096,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct NormalXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.NormalXor(background, source, amount); @@ -1817,6 +2117,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct MultiplyXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.MultiplyXor(background, source, amount); @@ -1835,6 +2138,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct AddXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.AddXor(background, source, amount); @@ -1853,6 +2159,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct SubtractXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.SubtractXor(background, source, amount); @@ -1871,6 +2180,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct ScreenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.ScreenXor(background, source, amount); @@ -1889,6 +2201,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct DarkenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.DarkenXor(background, source, amount); @@ -1907,6 +2222,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct LightenXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.LightenXor(background, source, amount); @@ -1925,6 +2243,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct OverlayXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.OverlayXor(background, source, amount); @@ -1943,6 +2264,9 @@ internal static class DefaultPixelBlenderOperators /// public readonly struct HardLightXor : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.HardLightXor(background, source, amount); @@ -1966,1299 +2290,543 @@ internal static class DefaultPixelBlenders where TPixel : unmanaged, IPixel { /// - /// Blends pixels with the "NormalSrc" composition equation. + /// Gets the shared blender for the "NormalSrc" composition equation. /// - public sealed class NormalSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrc Instance { get; } = new(); - } + public static PixelBlender NormalSrc => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrc" composition equation. + /// Gets the shared blender for the "MultiplySrc" composition equation. /// - public sealed class MultiplySrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrc Instance { get; } = new(); - } + public static PixelBlender MultiplySrc => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrc" composition equation. + /// Gets the shared blender for the "AddSrc" composition equation. /// - public sealed class AddSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrc Instance { get; } = new(); - } + public static PixelBlender AddSrc => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrc" composition equation. + /// Gets the shared blender for the "SubtractSrc" composition equation. /// - public sealed class SubtractSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrc Instance { get; } = new(); - } + public static PixelBlender SubtractSrc => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrc" composition equation. + /// Gets the shared blender for the "ScreenSrc" composition equation. /// - public sealed class ScreenSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrc Instance { get; } = new(); - } + public static PixelBlender ScreenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrc" composition equation. + /// Gets the shared blender for the "DarkenSrc" composition equation. /// - public sealed class DarkenSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrc Instance { get; } = new(); - } + public static PixelBlender DarkenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrc" composition equation. + /// Gets the shared blender for the "LightenSrc" composition equation. /// - public sealed class LightenSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrc Instance { get; } = new(); - } + public static PixelBlender LightenSrc => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrc" composition equation. + /// Gets the shared blender for the "OverlaySrc" composition equation. /// - public sealed class OverlaySrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrc Instance { get; } = new(); - } + public static PixelBlender OverlaySrc => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrc" composition equation. + /// Gets the shared blender for the "HardLightSrc" composition equation. /// - public sealed class HardLightSrc : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrc Instance { get; } = new(); - } + public static PixelBlender HardLightSrc => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcAtop" composition equation. + /// Gets the shared blender for the "NormalSrcAtop" composition equation. /// - public sealed class NormalSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcAtop Instance { get; } = new(); - } + public static PixelBlender NormalSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcAtop" composition equation. + /// Gets the shared blender for the "MultiplySrcAtop" composition equation. /// - public sealed class MultiplySrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcAtop Instance { get; } = new(); - } + public static PixelBlender MultiplySrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcAtop" composition equation. + /// Gets the shared blender for the "AddSrcAtop" composition equation. /// - public sealed class AddSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcAtop Instance { get; } = new(); - } + public static PixelBlender AddSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcAtop" composition equation. + /// Gets the shared blender for the "SubtractSrcAtop" composition equation. /// - public sealed class SubtractSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcAtop Instance { get; } = new(); - } + public static PixelBlender SubtractSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcAtop" composition equation. + /// Gets the shared blender for the "ScreenSrcAtop" composition equation. /// - public sealed class ScreenSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcAtop Instance { get; } = new(); - } + public static PixelBlender ScreenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcAtop" composition equation. + /// Gets the shared blender for the "DarkenSrcAtop" composition equation. /// - public sealed class DarkenSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcAtop Instance { get; } = new(); - } + public static PixelBlender DarkenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcAtop" composition equation. + /// Gets the shared blender for the "LightenSrcAtop" composition equation. /// - public sealed class LightenSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcAtop Instance { get; } = new(); - } + public static PixelBlender LightenSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcAtop" composition equation. + /// Gets the shared blender for the "OverlaySrcAtop" composition equation. /// - public sealed class OverlaySrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcAtop Instance { get; } = new(); - } + public static PixelBlender OverlaySrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcAtop" composition equation. + /// Gets the shared blender for the "HardLightSrcAtop" composition equation. /// - public sealed class HardLightSrcAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcAtop Instance { get; } = new(); - } + public static PixelBlender HardLightSrcAtop => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcOver" composition equation. + /// Gets the shared blender for the "NormalSrcOver" composition equation. /// - public sealed class NormalSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcOver Instance { get; } = new(); - } + public static PixelBlender NormalSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcOver" composition equation. + /// Gets the shared blender for the "MultiplySrcOver" composition equation. /// - public sealed class MultiplySrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcOver Instance { get; } = new(); - } + public static PixelBlender MultiplySrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcOver" composition equation. + /// Gets the shared blender for the "AddSrcOver" composition equation. /// - public sealed class AddSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcOver Instance { get; } = new(); - } + public static PixelBlender AddSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcOver" composition equation. + /// Gets the shared blender for the "SubtractSrcOver" composition equation. /// - public sealed class SubtractSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcOver Instance { get; } = new(); - } + public static PixelBlender SubtractSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcOver" composition equation. + /// Gets the shared blender for the "ScreenSrcOver" composition equation. /// - public sealed class ScreenSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcOver Instance { get; } = new(); - } + public static PixelBlender ScreenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcOver" composition equation. + /// Gets the shared blender for the "DarkenSrcOver" composition equation. /// - public sealed class DarkenSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcOver Instance { get; } = new(); - } + public static PixelBlender DarkenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcOver" composition equation. + /// Gets the shared blender for the "LightenSrcOver" composition equation. /// - public sealed class LightenSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcOver Instance { get; } = new(); - } + public static PixelBlender LightenSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcOver" composition equation. + /// Gets the shared blender for the "OverlaySrcOver" composition equation. /// - public sealed class OverlaySrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcOver Instance { get; } = new(); - } + public static PixelBlender OverlaySrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcOver" composition equation. + /// Gets the shared blender for the "HardLightSrcOver" composition equation. /// - public sealed class HardLightSrcOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcOver Instance { get; } = new(); - } + public static PixelBlender HardLightSrcOver => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcIn" composition equation. + /// Gets the shared blender for the "NormalSrcIn" composition equation. /// - public sealed class NormalSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcIn Instance { get; } = new(); - } + public static PixelBlender NormalSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcIn" composition equation. + /// Gets the shared blender for the "MultiplySrcIn" composition equation. /// - public sealed class MultiplySrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcIn Instance { get; } = new(); - } + public static PixelBlender MultiplySrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcIn" composition equation. + /// Gets the shared blender for the "AddSrcIn" composition equation. /// - public sealed class AddSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcIn Instance { get; } = new(); - } + public static PixelBlender AddSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcIn" composition equation. + /// Gets the shared blender for the "SubtractSrcIn" composition equation. /// - public sealed class SubtractSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcIn Instance { get; } = new(); - } + public static PixelBlender SubtractSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcIn" composition equation. + /// Gets the shared blender for the "ScreenSrcIn" composition equation. /// - public sealed class ScreenSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcIn Instance { get; } = new(); - } + public static PixelBlender ScreenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcIn" composition equation. + /// Gets the shared blender for the "DarkenSrcIn" composition equation. /// - public sealed class DarkenSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcIn Instance { get; } = new(); - } + public static PixelBlender DarkenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcIn" composition equation. + /// Gets the shared blender for the "LightenSrcIn" composition equation. /// - public sealed class LightenSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcIn Instance { get; } = new(); - } + public static PixelBlender LightenSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcIn" composition equation. + /// Gets the shared blender for the "OverlaySrcIn" composition equation. /// - public sealed class OverlaySrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcIn Instance { get; } = new(); - } + public static PixelBlender OverlaySrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcIn" composition equation. + /// Gets the shared blender for the "HardLightSrcIn" composition equation. /// - public sealed class HardLightSrcIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcIn Instance { get; } = new(); - } + public static PixelBlender HardLightSrcIn => PixelBlender.Instance; /// - /// Blends pixels with the "NormalSrcOut" composition equation. + /// Gets the shared blender for the "NormalSrcOut" composition equation. /// - public sealed class NormalSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalSrcOut Instance { get; } = new(); - } + public static PixelBlender NormalSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplySrcOut" composition equation. + /// Gets the shared blender for the "MultiplySrcOut" composition equation. /// - public sealed class MultiplySrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplySrcOut Instance { get; } = new(); - } + public static PixelBlender MultiplySrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "AddSrcOut" composition equation. + /// Gets the shared blender for the "AddSrcOut" composition equation. /// - public sealed class AddSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddSrcOut Instance { get; } = new(); - } + public static PixelBlender AddSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractSrcOut" composition equation. + /// Gets the shared blender for the "SubtractSrcOut" composition equation. /// - public sealed class SubtractSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractSrcOut Instance { get; } = new(); - } + public static PixelBlender SubtractSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenSrcOut" composition equation. + /// Gets the shared blender for the "ScreenSrcOut" composition equation. /// - public sealed class ScreenSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenSrcOut Instance { get; } = new(); - } + public static PixelBlender ScreenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenSrcOut" composition equation. + /// Gets the shared blender for the "DarkenSrcOut" composition equation. /// - public sealed class DarkenSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenSrcOut Instance { get; } = new(); - } + public static PixelBlender DarkenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "LightenSrcOut" composition equation. + /// Gets the shared blender for the "LightenSrcOut" composition equation. /// - public sealed class LightenSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenSrcOut Instance { get; } = new(); - } + public static PixelBlender LightenSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "OverlaySrcOut" composition equation. + /// Gets the shared blender for the "OverlaySrcOut" composition equation. /// - public sealed class OverlaySrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlaySrcOut Instance { get; } = new(); - } + public static PixelBlender OverlaySrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightSrcOut" composition equation. + /// Gets the shared blender for the "HardLightSrcOut" composition equation. /// - public sealed class HardLightSrcOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightSrcOut Instance { get; } = new(); - } + public static PixelBlender HardLightSrcOut => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDest" composition equation. + /// Gets the shared blender for the "NormalDest" composition equation. /// - public sealed class NormalDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDest Instance { get; } = new(); - } + public static PixelBlender NormalDest => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDest" composition equation. + /// Gets the shared blender for the "MultiplyDest" composition equation. /// - public sealed class MultiplyDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDest Instance { get; } = new(); - } + public static PixelBlender MultiplyDest => PixelBlender.Instance; /// - /// Blends pixels with the "AddDest" composition equation. + /// Gets the shared blender for the "AddDest" composition equation. /// - public sealed class AddDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDest Instance { get; } = new(); - } + public static PixelBlender AddDest => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDest" composition equation. + /// Gets the shared blender for the "SubtractDest" composition equation. /// - public sealed class SubtractDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDest Instance { get; } = new(); - } + public static PixelBlender SubtractDest => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDest" composition equation. + /// Gets the shared blender for the "ScreenDest" composition equation. /// - public sealed class ScreenDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDest Instance { get; } = new(); - } + public static PixelBlender ScreenDest => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDest" composition equation. + /// Gets the shared blender for the "DarkenDest" composition equation. /// - public sealed class DarkenDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDest Instance { get; } = new(); - } + public static PixelBlender DarkenDest => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDest" composition equation. + /// Gets the shared blender for the "LightenDest" composition equation. /// - public sealed class LightenDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDest Instance { get; } = new(); - } + public static PixelBlender LightenDest => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDest" composition equation. + /// Gets the shared blender for the "OverlayDest" composition equation. /// - public sealed class OverlayDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDest Instance { get; } = new(); - } + public static PixelBlender OverlayDest => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDest" composition equation. + /// Gets the shared blender for the "HardLightDest" composition equation. /// - public sealed class HardLightDest : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDest Instance { get; } = new(); - } + public static PixelBlender HardLightDest => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestAtop" composition equation. + /// Gets the shared blender for the "NormalDestAtop" composition equation. /// - public sealed class NormalDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestAtop Instance { get; } = new(); - } + public static PixelBlender NormalDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestAtop" composition equation. + /// Gets the shared blender for the "MultiplyDestAtop" composition equation. /// - public sealed class MultiplyDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestAtop Instance { get; } = new(); - } + public static PixelBlender MultiplyDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestAtop" composition equation. + /// Gets the shared blender for the "AddDestAtop" composition equation. /// - public sealed class AddDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestAtop Instance { get; } = new(); - } + public static PixelBlender AddDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestAtop" composition equation. + /// Gets the shared blender for the "SubtractDestAtop" composition equation. /// - public sealed class SubtractDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestAtop Instance { get; } = new(); - } + public static PixelBlender SubtractDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestAtop" composition equation. + /// Gets the shared blender for the "ScreenDestAtop" composition equation. /// - public sealed class ScreenDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestAtop Instance { get; } = new(); - } + public static PixelBlender ScreenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestAtop" composition equation. + /// Gets the shared blender for the "DarkenDestAtop" composition equation. /// - public sealed class DarkenDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestAtop Instance { get; } = new(); - } + public static PixelBlender DarkenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestAtop" composition equation. + /// Gets the shared blender for the "LightenDestAtop" composition equation. /// - public sealed class LightenDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestAtop Instance { get; } = new(); - } + public static PixelBlender LightenDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestAtop" composition equation. + /// Gets the shared blender for the "OverlayDestAtop" composition equation. /// - public sealed class OverlayDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestAtop Instance { get; } = new(); - } + public static PixelBlender OverlayDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestAtop" composition equation. + /// Gets the shared blender for the "HardLightDestAtop" composition equation. /// - public sealed class HardLightDestAtop : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestAtop Instance { get; } = new(); - } + public static PixelBlender HardLightDestAtop => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestOver" composition equation. + /// Gets the shared blender for the "NormalDestOver" composition equation. /// - public sealed class NormalDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestOver Instance { get; } = new(); - } + public static PixelBlender NormalDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestOver" composition equation. + /// Gets the shared blender for the "MultiplyDestOver" composition equation. /// - public sealed class MultiplyDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestOver Instance { get; } = new(); - } + public static PixelBlender MultiplyDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestOver" composition equation. + /// Gets the shared blender for the "AddDestOver" composition equation. /// - public sealed class AddDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestOver Instance { get; } = new(); - } + public static PixelBlender AddDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestOver" composition equation. + /// Gets the shared blender for the "SubtractDestOver" composition equation. /// - public sealed class SubtractDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestOver Instance { get; } = new(); - } + public static PixelBlender SubtractDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestOver" composition equation. + /// Gets the shared blender for the "ScreenDestOver" composition equation. /// - public sealed class ScreenDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestOver Instance { get; } = new(); - } + public static PixelBlender ScreenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestOver" composition equation. + /// Gets the shared blender for the "DarkenDestOver" composition equation. /// - public sealed class DarkenDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestOver Instance { get; } = new(); - } + public static PixelBlender DarkenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestOver" composition equation. + /// Gets the shared blender for the "LightenDestOver" composition equation. /// - public sealed class LightenDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestOver Instance { get; } = new(); - } + public static PixelBlender LightenDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestOver" composition equation. + /// Gets the shared blender for the "OverlayDestOver" composition equation. /// - public sealed class OverlayDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestOver Instance { get; } = new(); - } + public static PixelBlender OverlayDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestOver" composition equation. + /// Gets the shared blender for the "HardLightDestOver" composition equation. /// - public sealed class HardLightDestOver : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestOver Instance { get; } = new(); - } + public static PixelBlender HardLightDestOver => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestIn" composition equation. + /// Gets the shared blender for the "NormalDestIn" composition equation. /// - public sealed class NormalDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestIn Instance { get; } = new(); - } + public static PixelBlender NormalDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestIn" composition equation. + /// Gets the shared blender for the "MultiplyDestIn" composition equation. /// - public sealed class MultiplyDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestIn Instance { get; } = new(); - } + public static PixelBlender MultiplyDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestIn" composition equation. + /// Gets the shared blender for the "AddDestIn" composition equation. /// - public sealed class AddDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestIn Instance { get; } = new(); - } + public static PixelBlender AddDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestIn" composition equation. + /// Gets the shared blender for the "SubtractDestIn" composition equation. /// - public sealed class SubtractDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestIn Instance { get; } = new(); - } + public static PixelBlender SubtractDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestIn" composition equation. + /// Gets the shared blender for the "ScreenDestIn" composition equation. /// - public sealed class ScreenDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestIn Instance { get; } = new(); - } + public static PixelBlender ScreenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestIn" composition equation. + /// Gets the shared blender for the "DarkenDestIn" composition equation. /// - public sealed class DarkenDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestIn Instance { get; } = new(); - } + public static PixelBlender DarkenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestIn" composition equation. + /// Gets the shared blender for the "LightenDestIn" composition equation. /// - public sealed class LightenDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestIn Instance { get; } = new(); - } + public static PixelBlender LightenDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestIn" composition equation. + /// Gets the shared blender for the "OverlayDestIn" composition equation. /// - public sealed class OverlayDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestIn Instance { get; } = new(); - } + public static PixelBlender OverlayDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestIn" composition equation. + /// Gets the shared blender for the "HardLightDestIn" composition equation. /// - public sealed class HardLightDestIn : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestIn Instance { get; } = new(); - } + public static PixelBlender HardLightDestIn => PixelBlender.Instance; /// - /// Blends pixels with the "NormalDestOut" composition equation. + /// Gets the shared blender for the "NormalDestOut" composition equation. /// - public sealed class NormalDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalDestOut Instance { get; } = new(); - } + public static PixelBlender NormalDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyDestOut" composition equation. + /// Gets the shared blender for the "MultiplyDestOut" composition equation. /// - public sealed class MultiplyDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyDestOut Instance { get; } = new(); - } + public static PixelBlender MultiplyDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "AddDestOut" composition equation. + /// Gets the shared blender for the "AddDestOut" composition equation. /// - public sealed class AddDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddDestOut Instance { get; } = new(); - } + public static PixelBlender AddDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractDestOut" composition equation. + /// Gets the shared blender for the "SubtractDestOut" composition equation. /// - public sealed class SubtractDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractDestOut Instance { get; } = new(); - } + public static PixelBlender SubtractDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenDestOut" composition equation. + /// Gets the shared blender for the "ScreenDestOut" composition equation. /// - public sealed class ScreenDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenDestOut Instance { get; } = new(); - } + public static PixelBlender ScreenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenDestOut" composition equation. + /// Gets the shared blender for the "DarkenDestOut" composition equation. /// - public sealed class DarkenDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenDestOut Instance { get; } = new(); - } + public static PixelBlender DarkenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "LightenDestOut" composition equation. + /// Gets the shared blender for the "LightenDestOut" composition equation. /// - public sealed class LightenDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenDestOut Instance { get; } = new(); - } + public static PixelBlender LightenDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayDestOut" composition equation. + /// Gets the shared blender for the "OverlayDestOut" composition equation. /// - public sealed class OverlayDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayDestOut Instance { get; } = new(); - } + public static PixelBlender OverlayDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightDestOut" composition equation. + /// Gets the shared blender for the "HardLightDestOut" composition equation. /// - public sealed class HardLightDestOut : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightDestOut Instance { get; } = new(); - } + public static PixelBlender HardLightDestOut => PixelBlender.Instance; /// - /// Blends pixels with the "NormalClear" composition equation. + /// Gets the shared blender for the "NormalClear" composition equation. /// - public sealed class NormalClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalClear Instance { get; } = new(); - } + public static PixelBlender NormalClear => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyClear" composition equation. + /// Gets the shared blender for the "MultiplyClear" composition equation. /// - public sealed class MultiplyClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyClear Instance { get; } = new(); - } + public static PixelBlender MultiplyClear => PixelBlender.Instance; /// - /// Blends pixels with the "AddClear" composition equation. + /// Gets the shared blender for the "AddClear" composition equation. /// - public sealed class AddClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddClear Instance { get; } = new(); - } + public static PixelBlender AddClear => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractClear" composition equation. + /// Gets the shared blender for the "SubtractClear" composition equation. /// - public sealed class SubtractClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractClear Instance { get; } = new(); - } + public static PixelBlender SubtractClear => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenClear" composition equation. + /// Gets the shared blender for the "ScreenClear" composition equation. /// - public sealed class ScreenClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenClear Instance { get; } = new(); - } + public static PixelBlender ScreenClear => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenClear" composition equation. + /// Gets the shared blender for the "DarkenClear" composition equation. /// - public sealed class DarkenClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenClear Instance { get; } = new(); - } + public static PixelBlender DarkenClear => PixelBlender.Instance; /// - /// Blends pixels with the "LightenClear" composition equation. + /// Gets the shared blender for the "LightenClear" composition equation. /// - public sealed class LightenClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenClear Instance { get; } = new(); - } + public static PixelBlender LightenClear => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayClear" composition equation. + /// Gets the shared blender for the "OverlayClear" composition equation. /// - public sealed class OverlayClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayClear Instance { get; } = new(); - } + public static PixelBlender OverlayClear => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightClear" composition equation. + /// Gets the shared blender for the "HardLightClear" composition equation. /// - public sealed class HardLightClear : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightClear Instance { get; } = new(); - } + public static PixelBlender HardLightClear => PixelBlender.Instance; /// - /// Blends pixels with the "NormalXor" composition equation. + /// Gets the shared blender for the "NormalXor" composition equation. /// - public sealed class NormalXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static NormalXor Instance { get; } = new(); - } + public static PixelBlender NormalXor => PixelBlender.Instance; /// - /// Blends pixels with the "MultiplyXor" composition equation. + /// Gets the shared blender for the "MultiplyXor" composition equation. /// - public sealed class MultiplyXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static MultiplyXor Instance { get; } = new(); - } + public static PixelBlender MultiplyXor => PixelBlender.Instance; /// - /// Blends pixels with the "AddXor" composition equation. + /// Gets the shared blender for the "AddXor" composition equation. /// - public sealed class AddXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static AddXor Instance { get; } = new(); - } + public static PixelBlender AddXor => PixelBlender.Instance; /// - /// Blends pixels with the "SubtractXor" composition equation. + /// Gets the shared blender for the "SubtractXor" composition equation. /// - public sealed class SubtractXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static SubtractXor Instance { get; } = new(); - } + public static PixelBlender SubtractXor => PixelBlender.Instance; /// - /// Blends pixels with the "ScreenXor" composition equation. + /// Gets the shared blender for the "ScreenXor" composition equation. /// - public sealed class ScreenXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static ScreenXor Instance { get; } = new(); - } + public static PixelBlender ScreenXor => PixelBlender.Instance; /// - /// Blends pixels with the "DarkenXor" composition equation. + /// Gets the shared blender for the "DarkenXor" composition equation. /// - public sealed class DarkenXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static DarkenXor Instance { get; } = new(); - } + public static PixelBlender DarkenXor => PixelBlender.Instance; /// - /// Blends pixels with the "LightenXor" composition equation. + /// Gets the shared blender for the "LightenXor" composition equation. /// - public sealed class LightenXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static LightenXor Instance { get; } = new(); - } + public static PixelBlender LightenXor => PixelBlender.Instance; /// - /// Blends pixels with the "OverlayXor" composition equation. + /// Gets the shared blender for the "OverlayXor" composition equation. /// - public sealed class OverlayXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static OverlayXor Instance { get; } = new(); - } + public static PixelBlender OverlayXor => PixelBlender.Instance; /// - /// Blends pixels with the "HardLightXor" composition equation. + /// Gets the shared blender for the "HardLightXor" composition equation. /// - public sealed class HardLightXor : - DefaultPixelBlender - { - /// - /// Gets the shared blender instance. - /// - public static HardLightXor Instance { get; } = new(); - } + public static PixelBlender HardLightXor => PixelBlender.Instance; } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index ae0e0700d..e9bdbaf3c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -61,6 +61,9 @@ foreach (var composer in composers) /// public readonly struct <#= blenderComposer #> : IPixelBlenderOperator { + /// + public static bool IsAssociatedAlpha => false; + /// public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); @@ -95,16 +98,9 @@ foreach (var composer in composers) var blenderComposer = $"{blender}{composer}"; #> /// - /// Blends pixels with the "<#= blenderComposer #>" composition equation. + /// Gets the shared blender for the "<#= blenderComposer #>" composition equation. /// - public sealed class <#= blenderComposer #> : - DefaultPixelBlender> - { - /// - /// Gets the shared blender instance. - /// - public static <#= blenderComposer #> Instance { get; } = new(); - } + public static PixelBlender <#= blenderComposer #> => PixelBlender>.Instance; <# } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs index 10b588666..4b06e9523 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs @@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; /// internal interface IPixelBlenderOperator { + /// + /// Gets a value indicating whether pixel conversion uses associated-alpha vectors. + /// + public static abstract bool IsAssociatedAlpha { get; } + /// /// Blends one pixel represented by four RGBA lanes. /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs index 17282e388..ee8c6905f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs @@ -13,10 +13,75 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; /// /// The destination pixel format. /// The Porter-Duff equation. -internal abstract class PixelBlender : PixelBlender +internal sealed class PixelBlender : PixelBlender where TPixel : unmanaged, IPixel where TOperator : struct, IPixelBlenderOperator { + /// + /// Initializes a new instance of the class. + /// + private PixelBlender() + { + } + + /// + /// Gets the shared blender instance for this exact pixel and equation combination. + /// + // Keeping the singleton on the already-required closed blender type preserves per-mode lazy initialization + // without introducing another NativeAOT holder type or eagerly allocating every blender for a pixel format. + public static PixelBlender Instance { get; } = new PixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + // The operator type uniquely determines the alpha representation. NativeAOT therefore needs only the + // pixel/operator closure, while the JIT can remove this static choice from the generated hot path. + Vector4 backgroundVector = TOperator.IsAssociatedAlpha + ? background.ToAssociatedScaledVector4() + : background.ToUnassociatedScaledVector4(); + Vector4 sourceVector = TOperator.IsAssociatedAlpha + ? source.ToAssociatedScaledVector4() + : source.ToUnassociatedScaledVector4(); + + Vector4 result = TOperator.Invoke(backgroundVector, sourceVector, Numerics.Clamp(amount, 0, 1F)); + + return TOperator.IsAssociatedAlpha + ? TPixel.FromAssociatedScaledVector4(result) + : TPixel.FromUnassociatedScaledVector4(result); + } + + /// + protected sealed override void ToBlendVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha + ? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply + : PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + + PixelOperations.Instance.ToVector4(configuration, source, destination, modifiers); + } + + /// + protected sealed override Vector4 ToBlendVector4(TPixel source) + => TOperator.IsAssociatedAlpha + ? source.ToAssociatedScaledVector4() + : source.ToUnassociatedScaledVector4(); + + /// + protected sealed override void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + { + PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha + ? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply + : PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + + PixelOperations.Instance.FromVector4Destructive(configuration, source, destination, modifiers); + } + /// protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { @@ -519,22 +584,3 @@ internal abstract class PixelBlender : PixelBlender return Vector512.Min(Vector512.Max(Vector512.Zero, result), Vector512.Create(1F)); } } - -/// -/// Applies a statically selected Porter-Duff equation to straight-alpha pixels. -/// -/// The straight-alpha pixel format. -/// The Porter-Duff equation. -internal abstract class DefaultPixelBlender : PixelBlender - where TPixel : unmanaged, IPixel - where TOperator : struct, IPixelBlenderOperator -{ - /// - public sealed override TPixel Blend(TPixel background, TPixel source, float amount) - { - // The operator consumes the same unassociated, scaled representation used by the bulk conversion path. - Vector4 result = TOperator.Invoke(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1F)); - - return TPixel.FromUnassociatedScaledVector4(result); - } -} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index bf1391990..fe7de51c9 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -34,182 +34,182 @@ public partial class PixelOperations case PixelAlphaCompositionMode.Clear: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalClear.Instance; + default: return DefaultPixelBlenders.NormalClear; } case PixelAlphaCompositionMode.Xor: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalXor.Instance; + default: return DefaultPixelBlenders.NormalXor; } case PixelAlphaCompositionMode.Src: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrc.Instance; + default: return DefaultPixelBlenders.NormalSrc; } case PixelAlphaCompositionMode.SrcAtop: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcAtop.Instance; + default: return DefaultPixelBlenders.NormalSrcAtop; } case PixelAlphaCompositionMode.SrcIn: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcIn.Instance; + default: return DefaultPixelBlenders.NormalSrcIn; } case PixelAlphaCompositionMode.SrcOut: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOut.Instance; + default: return DefaultPixelBlenders.NormalSrcOut; } case PixelAlphaCompositionMode.Dest: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDest.Instance; + default: return DefaultPixelBlenders.NormalDest; } case PixelAlphaCompositionMode.DestAtop: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestAtop.Instance; + default: return DefaultPixelBlenders.NormalDestAtop; } case PixelAlphaCompositionMode.DestIn: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestIn.Instance; + default: return DefaultPixelBlenders.NormalDestIn; } case PixelAlphaCompositionMode.DestOut: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOut.Instance; + default: return DefaultPixelBlenders.NormalDestOut; } case PixelAlphaCompositionMode.DestOver: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOver.Instance; + default: return DefaultPixelBlenders.NormalDestOver; } case PixelAlphaCompositionMode.SrcOver: default: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOver.Instance; + default: return DefaultPixelBlenders.NormalSrcOver; } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 34606fcde..c3028b33b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -13,24 +13,24 @@ public class PixelBlenderTests { public static TheoryData BlenderMappings = new() { - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + { new TestPixel(), DefaultPixelBlenders.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal }, + { new TestPixel(), DefaultPixelBlenders.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen }, + { new TestPixel(), DefaultPixelBlenders.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight }, + { new TestPixel(), DefaultPixelBlenders.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay }, + { new TestPixel(), DefaultPixelBlenders.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken }, + { new TestPixel(), DefaultPixelBlenders.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten }, + { new TestPixel(), DefaultPixelBlenders.AddSrcOver.GetType(), PixelColorBlendingMode.Add }, + { new TestPixel(), DefaultPixelBlenders.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract }, + { new TestPixel(), DefaultPixelBlenders.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply }, + { new TestPixel(), DefaultPixelBlenders.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal }, + { new TestPixel(), DefaultPixelBlenders.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen }, + { new TestPixel(), DefaultPixelBlenders.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight }, + { new TestPixel(), DefaultPixelBlenders.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay }, + { new TestPixel(), DefaultPixelBlenders.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken }, + { new TestPixel(), DefaultPixelBlenders.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten }, + { new TestPixel(), DefaultPixelBlenders.AddSrcOver.GetType(), PixelColorBlendingMode.Add }, + { new TestPixel(), DefaultPixelBlenders.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract }, + { new TestPixel(), DefaultPixelBlenders.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply }, }; public static TheoryData BlenderModeMappings @@ -42,158 +42,158 @@ public class PixelBlenderTests AddBlenderModeMappings( data, PixelAlphaCompositionMode.Clear, - typeof(DefaultPixelBlenders.NormalClear), - typeof(DefaultPixelBlenders.MultiplyClear), - typeof(DefaultPixelBlenders.AddClear), - typeof(DefaultPixelBlenders.SubtractClear), - typeof(DefaultPixelBlenders.ScreenClear), - typeof(DefaultPixelBlenders.DarkenClear), - typeof(DefaultPixelBlenders.LightenClear), - typeof(DefaultPixelBlenders.OverlayClear), - typeof(DefaultPixelBlenders.HardLightClear)); + DefaultPixelBlenders.NormalClear.GetType(), + DefaultPixelBlenders.MultiplyClear.GetType(), + DefaultPixelBlenders.AddClear.GetType(), + DefaultPixelBlenders.SubtractClear.GetType(), + DefaultPixelBlenders.ScreenClear.GetType(), + DefaultPixelBlenders.DarkenClear.GetType(), + DefaultPixelBlenders.LightenClear.GetType(), + DefaultPixelBlenders.OverlayClear.GetType(), + DefaultPixelBlenders.HardLightClear.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.Xor, - typeof(DefaultPixelBlenders.NormalXor), - typeof(DefaultPixelBlenders.MultiplyXor), - typeof(DefaultPixelBlenders.AddXor), - typeof(DefaultPixelBlenders.SubtractXor), - typeof(DefaultPixelBlenders.ScreenXor), - typeof(DefaultPixelBlenders.DarkenXor), - typeof(DefaultPixelBlenders.LightenXor), - typeof(DefaultPixelBlenders.OverlayXor), - typeof(DefaultPixelBlenders.HardLightXor)); + DefaultPixelBlenders.NormalXor.GetType(), + DefaultPixelBlenders.MultiplyXor.GetType(), + DefaultPixelBlenders.AddXor.GetType(), + DefaultPixelBlenders.SubtractXor.GetType(), + DefaultPixelBlenders.ScreenXor.GetType(), + DefaultPixelBlenders.DarkenXor.GetType(), + DefaultPixelBlenders.LightenXor.GetType(), + DefaultPixelBlenders.OverlayXor.GetType(), + DefaultPixelBlenders.HardLightXor.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.Src, - typeof(DefaultPixelBlenders.NormalSrc), - typeof(DefaultPixelBlenders.MultiplySrc), - typeof(DefaultPixelBlenders.AddSrc), - typeof(DefaultPixelBlenders.SubtractSrc), - typeof(DefaultPixelBlenders.ScreenSrc), - typeof(DefaultPixelBlenders.DarkenSrc), - typeof(DefaultPixelBlenders.LightenSrc), - typeof(DefaultPixelBlenders.OverlaySrc), - typeof(DefaultPixelBlenders.HardLightSrc)); + DefaultPixelBlenders.NormalSrc.GetType(), + DefaultPixelBlenders.MultiplySrc.GetType(), + DefaultPixelBlenders.AddSrc.GetType(), + DefaultPixelBlenders.SubtractSrc.GetType(), + DefaultPixelBlenders.ScreenSrc.GetType(), + DefaultPixelBlenders.DarkenSrc.GetType(), + DefaultPixelBlenders.LightenSrc.GetType(), + DefaultPixelBlenders.OverlaySrc.GetType(), + DefaultPixelBlenders.HardLightSrc.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.SrcAtop, - typeof(DefaultPixelBlenders.NormalSrcAtop), - typeof(DefaultPixelBlenders.MultiplySrcAtop), - typeof(DefaultPixelBlenders.AddSrcAtop), - typeof(DefaultPixelBlenders.SubtractSrcAtop), - typeof(DefaultPixelBlenders.ScreenSrcAtop), - typeof(DefaultPixelBlenders.DarkenSrcAtop), - typeof(DefaultPixelBlenders.LightenSrcAtop), - typeof(DefaultPixelBlenders.OverlaySrcAtop), - typeof(DefaultPixelBlenders.HardLightSrcAtop)); + DefaultPixelBlenders.NormalSrcAtop.GetType(), + DefaultPixelBlenders.MultiplySrcAtop.GetType(), + DefaultPixelBlenders.AddSrcAtop.GetType(), + DefaultPixelBlenders.SubtractSrcAtop.GetType(), + DefaultPixelBlenders.ScreenSrcAtop.GetType(), + DefaultPixelBlenders.DarkenSrcAtop.GetType(), + DefaultPixelBlenders.LightenSrcAtop.GetType(), + DefaultPixelBlenders.OverlaySrcAtop.GetType(), + DefaultPixelBlenders.HardLightSrcAtop.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.SrcIn, - typeof(DefaultPixelBlenders.NormalSrcIn), - typeof(DefaultPixelBlenders.MultiplySrcIn), - typeof(DefaultPixelBlenders.AddSrcIn), - typeof(DefaultPixelBlenders.SubtractSrcIn), - typeof(DefaultPixelBlenders.ScreenSrcIn), - typeof(DefaultPixelBlenders.DarkenSrcIn), - typeof(DefaultPixelBlenders.LightenSrcIn), - typeof(DefaultPixelBlenders.OverlaySrcIn), - typeof(DefaultPixelBlenders.HardLightSrcIn)); + DefaultPixelBlenders.NormalSrcIn.GetType(), + DefaultPixelBlenders.MultiplySrcIn.GetType(), + DefaultPixelBlenders.AddSrcIn.GetType(), + DefaultPixelBlenders.SubtractSrcIn.GetType(), + DefaultPixelBlenders.ScreenSrcIn.GetType(), + DefaultPixelBlenders.DarkenSrcIn.GetType(), + DefaultPixelBlenders.LightenSrcIn.GetType(), + DefaultPixelBlenders.OverlaySrcIn.GetType(), + DefaultPixelBlenders.HardLightSrcIn.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.SrcOut, - typeof(DefaultPixelBlenders.NormalSrcOut), - typeof(DefaultPixelBlenders.MultiplySrcOut), - typeof(DefaultPixelBlenders.AddSrcOut), - typeof(DefaultPixelBlenders.SubtractSrcOut), - typeof(DefaultPixelBlenders.ScreenSrcOut), - typeof(DefaultPixelBlenders.DarkenSrcOut), - typeof(DefaultPixelBlenders.LightenSrcOut), - typeof(DefaultPixelBlenders.OverlaySrcOut), - typeof(DefaultPixelBlenders.HardLightSrcOut)); + DefaultPixelBlenders.NormalSrcOut.GetType(), + DefaultPixelBlenders.MultiplySrcOut.GetType(), + DefaultPixelBlenders.AddSrcOut.GetType(), + DefaultPixelBlenders.SubtractSrcOut.GetType(), + DefaultPixelBlenders.ScreenSrcOut.GetType(), + DefaultPixelBlenders.DarkenSrcOut.GetType(), + DefaultPixelBlenders.LightenSrcOut.GetType(), + DefaultPixelBlenders.OverlaySrcOut.GetType(), + DefaultPixelBlenders.HardLightSrcOut.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.Dest, - typeof(DefaultPixelBlenders.NormalDest), - typeof(DefaultPixelBlenders.MultiplyDest), - typeof(DefaultPixelBlenders.AddDest), - typeof(DefaultPixelBlenders.SubtractDest), - typeof(DefaultPixelBlenders.ScreenDest), - typeof(DefaultPixelBlenders.DarkenDest), - typeof(DefaultPixelBlenders.LightenDest), - typeof(DefaultPixelBlenders.OverlayDest), - typeof(DefaultPixelBlenders.HardLightDest)); + DefaultPixelBlenders.NormalDest.GetType(), + DefaultPixelBlenders.MultiplyDest.GetType(), + DefaultPixelBlenders.AddDest.GetType(), + DefaultPixelBlenders.SubtractDest.GetType(), + DefaultPixelBlenders.ScreenDest.GetType(), + DefaultPixelBlenders.DarkenDest.GetType(), + DefaultPixelBlenders.LightenDest.GetType(), + DefaultPixelBlenders.OverlayDest.GetType(), + DefaultPixelBlenders.HardLightDest.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.DestAtop, - typeof(DefaultPixelBlenders.NormalDestAtop), - typeof(DefaultPixelBlenders.MultiplyDestAtop), - typeof(DefaultPixelBlenders.AddDestAtop), - typeof(DefaultPixelBlenders.SubtractDestAtop), - typeof(DefaultPixelBlenders.ScreenDestAtop), - typeof(DefaultPixelBlenders.DarkenDestAtop), - typeof(DefaultPixelBlenders.LightenDestAtop), - typeof(DefaultPixelBlenders.OverlayDestAtop), - typeof(DefaultPixelBlenders.HardLightDestAtop)); + DefaultPixelBlenders.NormalDestAtop.GetType(), + DefaultPixelBlenders.MultiplyDestAtop.GetType(), + DefaultPixelBlenders.AddDestAtop.GetType(), + DefaultPixelBlenders.SubtractDestAtop.GetType(), + DefaultPixelBlenders.ScreenDestAtop.GetType(), + DefaultPixelBlenders.DarkenDestAtop.GetType(), + DefaultPixelBlenders.LightenDestAtop.GetType(), + DefaultPixelBlenders.OverlayDestAtop.GetType(), + DefaultPixelBlenders.HardLightDestAtop.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.DestIn, - typeof(DefaultPixelBlenders.NormalDestIn), - typeof(DefaultPixelBlenders.MultiplyDestIn), - typeof(DefaultPixelBlenders.AddDestIn), - typeof(DefaultPixelBlenders.SubtractDestIn), - typeof(DefaultPixelBlenders.ScreenDestIn), - typeof(DefaultPixelBlenders.DarkenDestIn), - typeof(DefaultPixelBlenders.LightenDestIn), - typeof(DefaultPixelBlenders.OverlayDestIn), - typeof(DefaultPixelBlenders.HardLightDestIn)); + DefaultPixelBlenders.NormalDestIn.GetType(), + DefaultPixelBlenders.MultiplyDestIn.GetType(), + DefaultPixelBlenders.AddDestIn.GetType(), + DefaultPixelBlenders.SubtractDestIn.GetType(), + DefaultPixelBlenders.ScreenDestIn.GetType(), + DefaultPixelBlenders.DarkenDestIn.GetType(), + DefaultPixelBlenders.LightenDestIn.GetType(), + DefaultPixelBlenders.OverlayDestIn.GetType(), + DefaultPixelBlenders.HardLightDestIn.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.DestOut, - typeof(DefaultPixelBlenders.NormalDestOut), - typeof(DefaultPixelBlenders.MultiplyDestOut), - typeof(DefaultPixelBlenders.AddDestOut), - typeof(DefaultPixelBlenders.SubtractDestOut), - typeof(DefaultPixelBlenders.ScreenDestOut), - typeof(DefaultPixelBlenders.DarkenDestOut), - typeof(DefaultPixelBlenders.LightenDestOut), - typeof(DefaultPixelBlenders.OverlayDestOut), - typeof(DefaultPixelBlenders.HardLightDestOut)); + DefaultPixelBlenders.NormalDestOut.GetType(), + DefaultPixelBlenders.MultiplyDestOut.GetType(), + DefaultPixelBlenders.AddDestOut.GetType(), + DefaultPixelBlenders.SubtractDestOut.GetType(), + DefaultPixelBlenders.ScreenDestOut.GetType(), + DefaultPixelBlenders.DarkenDestOut.GetType(), + DefaultPixelBlenders.LightenDestOut.GetType(), + DefaultPixelBlenders.OverlayDestOut.GetType(), + DefaultPixelBlenders.HardLightDestOut.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.DestOver, - typeof(DefaultPixelBlenders.NormalDestOver), - typeof(DefaultPixelBlenders.MultiplyDestOver), - typeof(DefaultPixelBlenders.AddDestOver), - typeof(DefaultPixelBlenders.SubtractDestOver), - typeof(DefaultPixelBlenders.ScreenDestOver), - typeof(DefaultPixelBlenders.DarkenDestOver), - typeof(DefaultPixelBlenders.LightenDestOver), - typeof(DefaultPixelBlenders.OverlayDestOver), - typeof(DefaultPixelBlenders.HardLightDestOver)); + DefaultPixelBlenders.NormalDestOver.GetType(), + DefaultPixelBlenders.MultiplyDestOver.GetType(), + DefaultPixelBlenders.AddDestOver.GetType(), + DefaultPixelBlenders.SubtractDestOver.GetType(), + DefaultPixelBlenders.ScreenDestOver.GetType(), + DefaultPixelBlenders.DarkenDestOver.GetType(), + DefaultPixelBlenders.LightenDestOver.GetType(), + DefaultPixelBlenders.OverlayDestOver.GetType(), + DefaultPixelBlenders.HardLightDestOver.GetType()); AddBlenderModeMappings( data, PixelAlphaCompositionMode.SrcOver, - typeof(DefaultPixelBlenders.NormalSrcOver), - typeof(DefaultPixelBlenders.MultiplySrcOver), - typeof(DefaultPixelBlenders.AddSrcOver), - typeof(DefaultPixelBlenders.SubtractSrcOver), - typeof(DefaultPixelBlenders.ScreenSrcOver), - typeof(DefaultPixelBlenders.DarkenSrcOver), - typeof(DefaultPixelBlenders.LightenSrcOver), - typeof(DefaultPixelBlenders.OverlaySrcOver), - typeof(DefaultPixelBlenders.HardLightSrcOver)); + DefaultPixelBlenders.NormalSrcOver.GetType(), + DefaultPixelBlenders.MultiplySrcOver.GetType(), + DefaultPixelBlenders.AddSrcOver.GetType(), + DefaultPixelBlenders.SubtractSrcOver.GetType(), + DefaultPixelBlenders.ScreenSrcOver.GetType(), + DefaultPixelBlenders.DarkenSrcOver.GetType(), + DefaultPixelBlenders.LightenSrcOver.GetType(), + DefaultPixelBlenders.OverlaySrcOver.GetType(), + DefaultPixelBlenders.HardLightSrcOver.GetType()); return data; } @@ -246,7 +246,7 @@ public class PixelBlenderTests [Fact] public void Blend_WithConstantSourceAndSingleAmount() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; Rgba32[] destination = new Rgba32[2]; Rgba32[] background = [ @@ -265,7 +265,7 @@ public class PixelBlenderTests [Fact] public void Blend_WithConstantSourceSingleAmountAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; Rgba32[] destination = new Rgba32[2]; Rgba32[] background = [ @@ -285,7 +285,7 @@ public class PixelBlenderTests [Fact] public void Blend_WithConstantSourceAndAmountSpan() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; Rgba32[] destination = new Rgba32[2]; Rgba32[] background = [ @@ -305,7 +305,7 @@ public class PixelBlenderTests [Fact] public void Blend_WithConstantSourceAmountSpanAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; Rgba32[] destination = new Rgba32[2]; Rgba32[] background = [ @@ -326,7 +326,7 @@ public class PixelBlenderTests [Fact] public void Blend_WithSourceSpanAmountSpanAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver(); + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; Rgba32[] destination = new Rgba32[2]; Rgba32[] background = [ @@ -352,7 +352,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithConstantSourceAndSingleAmount() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -374,7 +374,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithConstantSourceSingleAmountAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -397,7 +397,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithSourceSpanAndSingleAmount() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -425,7 +425,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithSourceSpanSingleAmountAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -454,7 +454,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithConstantSourceAndAmountSpan() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -477,7 +477,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithConstantSourceAmountSpanAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -501,7 +501,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithSourceSpanAndAmountSpan() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ @@ -530,7 +530,7 @@ public class PixelBlenderTests [Fact] public void BlendWithCoverage_WithSourceSpanAmountSpanAndWorkingBuffer() { - PixelBlender blender = new DefaultPixelBlenders.NormalSrc(); + PixelBlender blender = DefaultPixelBlenders.NormalSrc; Rgba32[] destination = new Rgba32[3]; Rgba32[] background = [ diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs index 153a9ac48..a2d571ad3 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -46,7 +46,7 @@ public class PorterDuffFunctionsTestsTPixel public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.NormalSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -56,7 +56,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.NormalSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -91,7 +91,7 @@ public class PorterDuffFunctionsTestsTPixel public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.MultiplySrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -101,7 +101,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.MultiplySrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -146,7 +146,7 @@ public class PorterDuffFunctionsTestsTPixel public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.AddSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -156,7 +156,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.AddSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -191,7 +191,7 @@ public class PorterDuffFunctionsTestsTPixel public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.SubtractSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -201,7 +201,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.SubtractSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -236,7 +236,7 @@ public class PorterDuffFunctionsTestsTPixel public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.ScreenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -246,7 +246,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.ScreenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -281,7 +281,7 @@ public class PorterDuffFunctionsTestsTPixel public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.DarkenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -291,7 +291,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.DarkenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -326,7 +326,7 @@ public class PorterDuffFunctionsTestsTPixel public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.LightenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -336,7 +336,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.LightenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -371,7 +371,7 @@ public class PorterDuffFunctionsTestsTPixel public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.OverlaySrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -381,7 +381,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.OverlaySrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) @@ -416,7 +416,7 @@ public class PorterDuffFunctionsTestsTPixel public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.HardLightSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -426,7 +426,7 @@ public class PorterDuffFunctionsTestsTPixel where TPixel : unmanaged, IPixel { TPixel[] dest = new TPixel[BulkBlendCount]; - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + DefaultPixelBlenders.HardLightSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); TPixel expectedPixel = expected.AsPixel(); foreach (TPixel pixel in dest) From 043c264aef235f6392871bba04990fa35a1c97d7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 17:28:43 +1000 Subject: [PATCH 239/240] Flatten JPEG ICC color conversion --- .../JpegColorConverter.CmykOperator.cs | 33 ---- .../JpegColorConverter.GrayScaleOperator.cs | 32 ---- .../JpegColorConverter.Operator.cs | 14 -- .../JpegColorConverter.RgbOperator.cs | 30 ---- .../JpegColorConverter.TiffCmykOperator.cs | 32 ---- .../JpegColorConverter.TiffYccKOperator.cs | 35 ---- .../JpegColorConverter.YCbCrOperator.cs | 34 ---- .../JpegColorConverter.YccKOperator.cs | 35 ---- .../JpegColorConverterBase.Icc.cs | 140 ++++++++++++++++ .../ColorConverters/JpegColorConverterBase.cs | 9 -- .../Formats/Jpg/JpegColorConverterTests.cs | 151 ++++++++++++++++++ 11 files changed, 291 insertions(+), 254 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs index 2f67e0f53..03469b2e6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs @@ -1,14 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -170,32 +164,5 @@ internal abstract partial class JpegColorConverterBase c2 = maximumValue - (y * maximumValue); c3 = maximumValue - k; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - // JPEG CMYK stores inverted components, while the ICC converter consumes normalized conventional CMYK. - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs index 8522fbf5c..6e5753d2f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs @@ -1,15 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -119,31 +113,5 @@ internal abstract partial class JpegColorConverterBase c2 = default; c3 = default; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - float scale = 1F / maximumValue; - - // ICC luminance values are normalized, so the source plane is scaled in place before conversion. - TensorPrimitives_.Multiply(c0, scale, c0); - - Span source = MemoryMarshal.Cast(c0); - Span destination = MemoryMarshal.Cast(packed); - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs index 0058a6213..3b2ab914b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.Operator.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -137,15 +136,6 @@ internal abstract partial class JpegColorConverterBase /// The third converted component lanes. /// The fourth converted component lanes, if used. public static abstract void ConvertFromRgb(Vector512 r, Vector512 g, Vector512 b, Vector512 maximumValue, Vector512 halfValue, Vector512 scale, out Vector512 c0, out Vector512 c1, out Vector512 c2, out Vector512 c3); - - /// - /// Converts JPEG component values to RGB using the supplied ICC profile. - /// - /// The configuration used to allocate temporary storage. - /// The source ICC profile. - /// The component values to convert. - /// The maximum component value for the configured precision. - public static abstract void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue); } /// @@ -285,10 +275,6 @@ internal abstract partial class JpegColorConverterBase } } - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TOperator.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - /// public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs index a97144de9..7f05a5d08 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs @@ -1,14 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -109,29 +103,5 @@ internal abstract partial class JpegColorConverterBase c2 = b; c3 = default; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - // JPEG planes use the integer sample domain, while ICC RGB values are normalized and interleaved. - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); - - Span rgb = MemoryMarshal.Cast(packed); - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - ColorProfileConverter converter = new(options); - converter.Convert(rgb, rgb); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..rgb.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs index f2c20959d..a3ba9ecb7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs @@ -1,14 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -156,31 +150,5 @@ internal abstract partial class JpegColorConverterBase c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; c3 = k; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - // TIFF CMYK is already non-inverted, so only normalization and interleaving precede ICC conversion. - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs index 63e14fb88..90cc84048 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs @@ -1,15 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -184,34 +178,5 @@ internal abstract partial class JpegColorConverterBase c2 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue); c3 = k * maximumValue; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - // TIFF YccK is non-inverted, so normalize directly before converting its JPEG-specific model to CMYK. - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs index 9ceb7b44b..e5bb6db03 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs @@ -1,15 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -172,33 +166,5 @@ internal abstract partial class JpegColorConverterBase c2 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); c3 = default; } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - // ICC profiles rarely expose YCbCr transforms, so BT.601 first produces RGB in the profile's source space. - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - converter.Convert(source, destination); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - converter = new ColorProfileConverter(options); - converter.Convert(destination, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs index 003c77227..90bee0c94 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs @@ -1,15 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -136,34 +130,5 @@ internal abstract partial class JpegColorConverterBase YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); } - - /// - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - // Adobe-style JPEG YccK is inverted; normalize it before applying the format-defined YccK-to-CMYK transform. - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(packed); - converter.Convert(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs new file mode 100644 index 000000000..37f1297a6 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. +#nullable disable + +using System.Buffers; +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + /// + /// Converts planar jpeg component values in to RGB color space in-place using the given ICC profile. + /// + /// The configuration instance to use for the conversion. + /// The input/output as a stack-only struct. + /// The ICC profile to use for the conversion. + public void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + int length = c0.Length; + + // Four-component JPEG models need room for an interleaved CMYK or YccK source. Every conversion + // finishes with three packed RGB floats, which safely occupy the start of the same temporary buffer. + bool hasFourthComponent = this.ColorSpace is JpegColorSpace.Ycck + or JpegColorSpace.Cmyk + or JpegColorSpace.TiffYccK + or JpegColorSpace.TiffCmyk; + int packedComponentCount = hasFourthComponent ? 4 : 3; + + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(length * packedComponentCount); + Span packed = memoryOwner.Memory.Span; + + if (this.ColorSpace == JpegColorSpace.Grayscale) + { + // The single luminance plane is the ICC source, so it is normalized in place. The temporary + // buffer is still RGB-sized because the profile conversion expands each Y sample to three lanes. + TensorPrimitives_.Multiply(c0, 1F / this.MaximumValue, c0); + + Span source = MemoryMarshal.Cast(c0); + Span destination = MemoryMarshal.Cast(packed); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..length], c0, c1, c2); + return; + } + + // RGB and YCbCr become packed RGB before the profile transform. CMYK models remain packed CMYK, + // because their source profile describes that four-component space rather than an intermediate RGB space. + bool profileSourceIsCmyk = false; + + switch (this.ColorSpace) + { + case JpegColorSpace.RGB: + // JPEG RGB planes use the integer sample domain; ICC RGB values use normalized interleaved lanes. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / this.MaximumValue); + break; + + case JpegColorSpace.YCbCr: + // ICC profiles rarely expose YCbCr transforms. BT.601 therefore produces RGB in the profile's + // source space first, and that packed RGB becomes the input to the profile transform below. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / this.MaximumValue); + + ColorProfileConverter yCbCrConverter = new(); + Span yCbCr = MemoryMarshal.Cast(packed); + Span yCbCrDestination = MemoryMarshal.Cast(packed); + yCbCrConverter.Convert(yCbCr, yCbCrDestination); + break; + + case JpegColorSpace.Cmyk: + // Adobe-style JPEG CMYK stores inverted samples, while ICC consumes conventional normalized CMYK. + PackedInvertNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); + profileSourceIsCmyk = true; + break; + + case JpegColorSpace.TiffCmyk: + // TIFF JPEG CMYK is already non-inverted, so only normalization and interleaving are required. + PackedNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); + profileSourceIsCmyk = true; + break; + + case JpegColorSpace.Ycck: + // Adobe-style JPEG YccK is inverted before its format-defined YccK-to-CMYK transform. + PackedInvertNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); + + ColorProfileConverter yccKConverter = new(); + Span yccKCmyk = MemoryMarshal.Cast(packed); + yccKConverter.Convert(MemoryMarshal.Cast(yccKCmyk), yccKCmyk); + profileSourceIsCmyk = true; + break; + + case JpegColorSpace.TiffYccK: + // TIFF JPEG YccK is non-inverted, but otherwise uses the same YccK-to-CMYK transform. + PackedNormalizeInterleave4(c0, c1, c2, values.Component3, packed, this.MaximumValue); + + ColorProfileConverter tiffYccKConverter = new(); + Span tiffYccKCmyk = MemoryMarshal.Cast(packed); + tiffYccKConverter.Convert(MemoryMarshal.Cast(tiffYccKCmyk), tiffYccKCmyk); + profileSourceIsCmyk = true; + break; + } + + ColorConversionOptions profileOptions = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter profileConverter = new(profileOptions); + Span rgb = MemoryMarshal.Cast(packed)[..length]; + + if (profileSourceIsCmyk) + { + // The destination aliases the first three floats of each four-float source item. The converter + // supports this established in-place contraction, and the source span retains its original length. + profileConverter.Convert(MemoryMarshal.Cast(packed), rgb); + } + else + { + profileConverter.Convert(rgb, rgb); + } + + // Only the packed RGB prefix is meaningful after four-component conversion; scatter it back to the + // decoder's three planar output buffers while leaving the fourth source component untouched. + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..length], c0, c1, c2); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 36adf8f75..2b1271c82 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -3,7 +3,6 @@ #nullable disable using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -81,14 +80,6 @@ internal abstract partial class JpegColorConverterBase /// The input/output as a stack-only struct public abstract void ConvertToRgbInPlace(in ComponentValues values); - /// - /// Converts planar jpeg component values in to RGB color space in-place using the given ICC profile. - /// - /// The configuration instance to use for the conversion. - /// The input/output as a stack-only struct. - /// The ICC profile to use for the conversion. - public abstract void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile); - /// /// Converts RGB lanes to jpeg component values. /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 76cd2a794..de42e85d7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,8 +2,11 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.ColorProfiles; +using SixLabors.ImageSharp.Tests.ColorProfiles.Icc; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -168,6 +171,133 @@ public class JpegColorConverterTests } } + /// + /// Verifies the flattened ICC traversal against independently composed color-model and profile conversions. + /// + /// The JPEG color space. + /// The number of component planes owned by the color model. + /// The JPEG sample precision. + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1, 8)] + [InlineData(JpegColorSpace.Grayscale, 1, 12)] + [InlineData(JpegColorSpace.RGB, 3, 8)] + [InlineData(JpegColorSpace.RGB, 3, 12)] + [InlineData(JpegColorSpace.YCbCr, 3, 8)] + [InlineData(JpegColorSpace.YCbCr, 3, 12)] + [InlineData(JpegColorSpace.Cmyk, 4, 8)] + [InlineData(JpegColorSpace.Cmyk, 4, 12)] + [InlineData(JpegColorSpace.Ycck, 4, 8)] + [InlineData(JpegColorSpace.Ycck, 4, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] + [InlineData(JpegColorSpace.TiffYccK, 4, 8)] + [InlineData(JpegColorSpace.TiffYccK, 4, 12)] + internal void ConvertToRgbWithIccMatchesColorProfileDefinition(JpegColorSpace colorSpace, int componentCount, int precision) + { + const int length = 8; + JpegColorConverterBase.ComponentValues source = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); + + // YccK and CMYK profiles describe the conventional CMYK values produced after JPEG inversion + // and any YccK model transform. Three-component models use an RGB profile, while grayscale + // needs a one-channel profile so the profile's declared data space agrees with the Y input. + IccProfile profile = colorSpace switch + { + JpegColorSpace.Grayscale => CreateGrayProfile(), + JpegColorSpace.Cmyk or JpegColorSpace.Ycck or JpegColorSpace.TiffCmyk or JpegColorSpace.TiffYccK + => TestIccProfiles.GetProfile(TestIccProfiles.Fogra39), + _ => CompactSrgbV4Profile.Profile, + }; + + converter.ConvertToRgbInPlaceWithIcc(Configuration.Default, actual, profile); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter profileConverter = new(options); + ColorProfileConverter modelConverter = new(); + float maximumValue = MathF.Pow(2, precision) - 1; + Rgb[] grayscaleExpected = null; + + if (colorSpace == JpegColorSpace.Grayscale) + { + // The profile converter has distinct scalar and span entry points. Production converts the complete + // grayscale row, so the independent reference must exercise that same public span contract. + Y[] luminance = new Y[length]; + grayscaleExpected = new Rgb[length]; + + for (int i = 0; i < length; i++) + { + luminance[i] = new Y(source.Component0[i] / maximumValue); + } + + profileConverter.Convert(luminance, grayscaleExpected); + } + + for (int i = 0; i < length; i++) + { + float c0 = source.Component0[i] / maximumValue; + float c1 = componentCount >= 2 ? source.Component1[i] / maximumValue : 0; + float c2 = componentCount >= 3 ? source.Component2[i] / maximumValue : 0; + float c3 = componentCount == 4 ? source.Component3[i] / maximumValue : 0; + Rgb expected; + + switch (colorSpace) + { + case JpegColorSpace.Grayscale: + expected = grayscaleExpected[i]; + break; + + case JpegColorSpace.RGB: + expected = profileConverter.Convert(new Rgb(c0, c1, c2)); + break; + + case JpegColorSpace.YCbCr: + Rgb rgb = modelConverter.Convert(new YCbCr(c0, c1, c2)); + expected = profileConverter.Convert(rgb); + break; + + case JpegColorSpace.Cmyk: + expected = profileConverter.Convert(new Cmyk(1F - c0, 1F - c1, 1F - c2, 1F - c3)); + break; + + case JpegColorSpace.Ycck: + Cmyk cmyk = modelConverter.Convert(new YccK(1F - c0, 1F - c1, 1F - c2, 1F - c3)); + expected = profileConverter.Convert(cmyk); + break; + + case JpegColorSpace.TiffCmyk: + expected = profileConverter.Convert(new Cmyk(c0, c1, c2, c3)); + break; + + case JpegColorSpace.TiffYccK: + Cmyk tiffCmyk = modelConverter.Convert(new YccK(c0, c1, c2, c3)); + expected = profileConverter.Convert(tiffCmyk); + break; + + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + return; + } + + if (colorSpace == JpegColorSpace.Grayscale) + { + // Grayscale exposes one physical component plane through all three component views. Deinterleaving + // therefore leaves the final blue write in that plane, matching the established converter contract. + Assert.Equal(expected.B, actual.Component0[i], ToRgbTolerance); + } + else + { + Assert.Equal(expected.R, actual.Component0[i], ToRgbTolerance); + Assert.Equal(expected.G, actual.Component1[i], ToRgbTolerance); + Assert.Equal(expected.B, actual.Component2[i], ToRgbTolerance); + } + } + } + /// /// Verifies that the shared converter retains its scalar behavior when hardware intrinsics are disabled. /// @@ -400,6 +530,27 @@ public class JpegColorConverterTests return values; } + /// + /// Creates an identity-transfer grayscale profile for the one-channel ICC test path. + /// + /// The grayscale ICC profile. + private static IccProfile CreateGrayProfile() + { + IccProfileHeader header = new() + { + Class = IccProfileClass.InputDevice, + DataColorSpace = IccColorSpaceType.Gray, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.MediaRelativeColorimetric, + Version = new IccVersion(4, 3, 0), + }; + + // An empty ICC curve is the standard identity transfer function. Tagging it as GrayTrc gives + // the profile converter a complete one-channel device-to-PCS path without test-only LUT data. + IccTagDataEntry[] entries = [new IccCurveTagDataEntry(IccProfileTag.GrayTrc)]; + return new IccProfile(header, entries); + } + /// /// Compares one converted sample with an independent definition of its JPEG color model. /// From 14cf138cbb2eeb24a1e89f7b84548be17f5ce560 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Jul 2026 19:20:20 +1000 Subject: [PATCH 240/240] Validate tensor primitive span contracts --- .../Common/Helpers/TensorPrimitives_.Add.cs | 9 ++ .../Common/Helpers/TensorPrimitives_.Clamp.cs | 4 + .../Helpers/TensorPrimitives_.Divide.cs | 4 + .../Helpers/TensorPrimitives_.Helpers.cs | 76 +++++++++++++++++ .../Common/Helpers/TensorPrimitives_.Max.cs | 4 + .../Helpers/TensorPrimitives_.Multiply.cs | 4 + .../Helpers/TensorPrimitives_.Negate.cs | 11 +++ .../Common/TensorPrimitivesTests.cs | 82 +++++++++++++++++++ 8 files changed, 194 insertions(+) diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs index f2ef214cf..19f46c06f 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs @@ -16,6 +16,11 @@ internal static partial class TensorPrimitives_ /// The first addends. /// The second addends. /// The destination for the sums. + /// and do not have the same length. + /// is shorter than the input spans. + /// + /// An input and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ReadOnlySpan x, ReadOnlySpan y, Span destination) where T : IAdditionOperators, IAdditiveIdentity @@ -28,6 +33,10 @@ internal static partial class TensorPrimitives_ /// The first addends. /// The scalar second addend. /// The destination for the sums. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add(ReadOnlySpan x, T y, Span destination) where T : IAdditionOperators, IAdditiveIdentity diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs index e2cc9097e..b974163dc 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs @@ -18,6 +18,10 @@ internal static partial class TensorPrimitives_ /// The inclusive lower bound. /// The inclusive upper bound. /// The destination for the clamped values. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Clamp(ReadOnlySpan x, T min, T max, Span destination) where T : INumber diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs index e40a26cea..f4a5bd01a 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs @@ -16,6 +16,10 @@ internal static partial class TensorPrimitives_ /// The dividend values. /// The divisor. /// The destination for the quotient values. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Divide(ReadOnlySpan x, T y, Span destination) where T : IDivisionOperators diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs index 3ba1b61af..e80ca5411 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -109,6 +110,47 @@ internal static partial class TensorPrimitives_ public static abstract Vector512 Invoke(Vector512 x, Vector512 y, Vector512 z); } + /// + /// Validates that an input and destination are either disjoint or begin at the same memory location. + /// + /// The element type. + /// The input values. + /// The destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateInputOutputSpanNonOverlapping(ReadOnlySpan input, Span destination) + { + // Runtime TensorPrimitives permits exact same-start overlap for in-place operation. A shifted overlap is + // rejected because forward SIMD stores could overwrite input elements before a later load consumes them. + if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(destination)) + && input.Overlaps(destination)) + { + ThrowInputAndDestinationSpanMustNotOverlap(); + } + } + + /// + /// Throws when input spans do not have the same length. + /// + [DoesNotReturn] + private static void ThrowSpansMustHaveSameLength() + => throw new ArgumentException("Input span arguments must all have the same length."); + + /// + /// Throws when the destination cannot hold every result. + /// + [DoesNotReturn] + private static void ThrowDestinationTooShort() + => throw new ArgumentException("Destination is too short.", "destination"); + + /// + /// Throws when an input and destination overlap without beginning at the same memory location. + /// + [DoesNotReturn] + private static void ThrowInputAndDestinationSpanMustNotOverlap() + => throw new ArgumentException( + "The destination span may only overlap with an input span if the two spans start at the same memory location.", + "destination"); + /// /// Performs an element-wise binary operation between two spans. /// @@ -124,6 +166,19 @@ internal static partial class TensorPrimitives_ Span destination) where TOperator : struct, IBinaryOperator { + if (x.Length != y.Length) + { + ThrowSpansMustHaveSameLength(); + } + + if (x.Length > destination.Length) + { + ThrowDestinationTooShort(); + } + + ValidateInputOutputSpanNonOverlapping(x, destination); + ValidateInputOutputSpanNonOverlapping(y, destination); + ref T xRef = ref MemoryMarshal.GetReference(x); ref T yRef = ref MemoryMarshal.GetReference(y); ref T destinationRef = ref MemoryMarshal.GetReference(destination); @@ -173,6 +228,13 @@ internal static partial class TensorPrimitives_ Span destination) where TOperator : struct, IBinaryOperator { + if (x.Length > destination.Length) + { + ThrowDestinationTooShort(); + } + + ValidateInputOutputSpanNonOverlapping(x, destination); + ref T xRef = ref MemoryMarshal.GetReference(x); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; @@ -220,6 +282,13 @@ internal static partial class TensorPrimitives_ Span destination) where TOperator : struct, IBinaryOperator { + if (x.Length > destination.Length) + { + ThrowDestinationTooShort(); + } + + ValidateInputOutputSpanNonOverlapping(x, destination); + ref T xRef = ref MemoryMarshal.GetReference(x); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; @@ -282,6 +351,13 @@ internal static partial class TensorPrimitives_ Span destination) where TOperator : struct, ITernaryOperator { + if (x.Length > destination.Length) + { + ThrowDestinationTooShort(); + } + + ValidateInputOutputSpanNonOverlapping(x, destination); + ref T xRef = ref MemoryMarshal.GetReference(x); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs index 31312dddf..fc621a122 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs @@ -16,6 +16,10 @@ internal static partial class TensorPrimitives_ /// The values to compare. /// The value to compare with each element. /// The destination for the maximum values. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Max(ReadOnlySpan x, T y, Span destination) where T : INumber diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs index deb1e922e..367a7ab0b 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs @@ -16,6 +16,10 @@ internal static partial class TensorPrimitives_ /// The multiplicands. /// The multiplier. /// The destination for the products. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Multiply(ReadOnlySpan x, T y, Span destination) where T : IMultiplyOperators, IMultiplicativeIdentity diff --git a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs index 99dc80184..05f93ac66 100644 --- a/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs +++ b/src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs @@ -56,6 +56,10 @@ internal static partial class TensorPrimitives_ /// The element type. /// The values to negate. /// The destination for the negated values. + /// is shorter than . + /// + /// and overlap without beginning at the same memory location. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Negate(ReadOnlySpan x, Span destination) where T : IUnaryNegationOperators @@ -72,6 +76,13 @@ internal static partial class TensorPrimitives_ private static void InvokeSpanIntoSpan(ReadOnlySpan x, Span destination) where TOperator : struct, IUnaryOperator { + if (x.Length > destination.Length) + { + ThrowDestinationTooShort(); + } + + ValidateInputOutputSpanNonOverlapping(x, destination); + ref T xRef = ref MemoryMarshal.GetReference(x); ref T destinationRef = ref MemoryMarshal.GetReference(destination); nuint length = (uint)x.Length; diff --git a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs index 5ce5401f8..b2f4dfe76 100644 --- a/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs @@ -51,6 +51,88 @@ public class TensorPrimitivesTests | HwIntrinsics.DisableArm64Sve | HwIntrinsics.DisableHWIntrinsic); + /// + /// Verifies that span-to-span operations require equal input lengths before accessing either input. + /// + [Fact] + public void AddSpanSpanRejectsMismatchedInputLengths() + { + ArgumentException exception = Assert.Throws( + () => TensorPrimitives_.Add(new int[4], new int[3], new int[4])); + + Assert.Null(exception.ParamName); + } + + /// + /// Verifies that every operation rejects a destination that cannot hold all input elements. + /// + [Fact] + public void OperationsRejectShortDestinations() + { + int[] integers = new int[4]; + float[] singles = new float[4]; + + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Add(integers, integers, new int[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Add(integers, 1, new int[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Multiply(singles, 2F, new float[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Divide(singles, 2F, new float[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Max(singles, 2F, new float[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Clamp(singles, 0F, 1F, new float[3])).ParamName); + Assert.Equal("destination", Assert.Throws(() => TensorPrimitives_.Negate(singles, new float[3])).ParamName); + } + + /// + /// Verifies that every operation rejects shifted input and destination overlap. + /// + [Fact] + public void OperationsRejectShiftedOverlap() + { + int[] integers = new int[5]; + int[] separateIntegers = new int[4]; + float[] singles = new float[5]; + + // Both inputs need independent validation because either one may alias a shifted destination. + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Add(integers.AsSpan(0, 4), separateIntegers, integers.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Add(separateIntegers, integers.AsSpan(0, 4), integers.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Add(integers.AsSpan(0, 4), 1, integers.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Multiply(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Divide(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Max(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Clamp(singles.AsSpan(0, 4), 0F, 1F, singles.AsSpan(1, 4))).ParamName); + + Assert.Equal( + "destination", + Assert.Throws( + () => TensorPrimitives_.Negate(singles.AsSpan(0, 4), singles.AsSpan(1, 4))).ParamName); + } + /// /// Runs the TensorPrimitives compatibility assertions inside a process configured for one hardware-intrinsic tier. ///