Browse Source

Merge branch 'main' into png-cgbi-improvements

pull/3137/head
James Jackson-South 1 week ago
committed by GitHub
parent
commit
cef0402233
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      .github/workflows/build-and-test.yml
  2. 8
      .github/workflows/code-coverage.yml
  3. 96
      SECURITY.md
  4. 54892
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  5. 444
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  6. 59
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  7. 461
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  8. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs
  9. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs
  10. 16
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs
  11. 45
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude
  12. 221
      tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

14
.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

8
.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

96
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.

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

File diff suppressed because it is too large

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save