diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5151e672fc..2156982f8c 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 @@ -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 }} @@ -137,14 +137,14 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive # 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 @@ -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 @@ -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 fe2e4e5ca7..0e55bbe7de 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 @@ -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 @@ -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 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..8b2e1df860 --- /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. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 883693031e..05db95917e 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 c2439c24cc..e20e47f2bb 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 948076fa32..b764432e8a 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 fcd28796fb..0b1a51d74f 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 a5e4e3048b..bcfad0117c 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 30fed8d1c1..3ef93de9fc 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 ae6be44012..6afcb64326 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 ae29223deb..d53ed520b6 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 ddd79bb756..36ddad6c64 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)); } }